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.
# status.py - working copy browser## Copyright 2010 Steve Borho <steve@borho.org>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference.importosfrommercurialimportui,hg,util,patch,cmdutil,error,mdifffrommercurialimportcontext,merge,commands,subrepofromtortoisehg.hgqtimportqtlib,htmlui,chunkselect,wctxactions,visdifffromtortoisehg.utilimportpaths,hglibfromtortoisehg.hgqt.i18nimport_fromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*# This widget can be used as the basis of the commit tool or any other# working copy browser.# Technical Debt# We need a real icon set for file status types# Context menu should offer rename guesses# Thread rowSelected, connect to an external progress bar# Chunk selection, tri-state checkboxes for commit# TreeView steals CTRL-ENTER from parent widgets # Maybe, Maybe Not# Investigate folding/nesting of files# Toolbar# double-click visual diffsCOL_PATH=0COL_STATUS=1COL_MERGE_STATE=2COL_PATH_DISPLAY=3COL_EXTENSION=4COL_SIZE=5_colors={}classStatusWidget(QWidget):'''Working copy status widget SIGNALS: loadBegin() - for progress bar loadComplete() - for progress bar errorMessage(QString) - for status bar titleTextChanged(QString) - for window title '''def__init__(self,pats,opts,root=None,parent=None):QWidget.__init__(self,parent)root=paths.find_root(root)assert(root)self.repo=hg.repository(ui.ui(),path=root)self.wctx=self.repo[None]self.opts=dict(modified=True,added=True,removed=True,deleted=True,unknown=True,clean=False,ignored=False,subrepo=True)self.opts.update(opts)self.pats=patsself.ms={}self.curRow=Noneself.patchecked={}self.refreshing=None# determine the user configured status colors# (in the future, we could support full rich-text tags)qtlib.configstyles(self.repo.ui)labels=[(stat,val.uilabel)forstat,valinstatusTypes.items()]labels.extend([('r','resolve.resolved'),('u','resolve.unresolved')])forstat,labelinlabels:effect=qtlib.geteffect(label)foreineffect.split(';'):ife.startswith('color:'):_colors[stat]=QColor(e[7:])breakSP=QSizePolicysplit=QSplitter(Qt.Horizontal)layout=QVBoxLayout()layout.setMargin(0)layout.addWidget(split)self.setLayout(layout)vbox=QVBoxLayout()vbox.setMargin(0)frame=QFrame(split)sp=SP(SP.Expanding,SP.Expanding)sp.setHorizontalStretch(0)sp.setVerticalStretch(0)frame.setSizePolicy(sp)frame.setLayout(vbox)hbox=QHBoxLayout()hbox.setContentsMargins(5,0,0,0)lbl=QLabel(_('Filter:'))le=QLineEdit()pb=QPushButton(_('MAR!?IC'))# needs a better labelhbox.addWidget(lbl)hbox.addWidget(le)hbox.addWidget(pb)tv=WctxFileTree(self.repo)vbox.addLayout(hbox)vbox.addWidget(tv)split.addWidget(frame)ifself.pats:defclearPattern():self.pats=[]self.refreshWctx()cpb.setVisible(False)self.emit(SIGNAL('titleTextChanged'),QString(self.getTitle()))cpb=QPushButton(_('Remove filter, show root'))vbox.addWidget(cpb)cpb.clicked.connect(clearPattern)self.countlbl=QLabel()hcbox=QHBoxLayout()vbox.addLayout(hcbox)hcbox.addSpacing(6)hcbox.addWidget(self.countlbl)self.connect(tv,SIGNAL('clicked(QModelIndex)'),self.rowSelected)self.connect(tv,SIGNAL('menuAction()'),self.refreshWctx)tv.setItemsExpandable(False)tv.setRootIsDecorated(False)tv.sortByColumn(COL_PATH_DISPLAY)defsetButtonText():text=''forstatinStatusType.preferredOrder:name=statusTypes[stat].nameifself.opts[name]:text+=statpb.setText(text)defstatusTypeTrigger(isChecked):txt=hglib.fromunicode(self.sender().text())self.opts[txt[2:]]=isCheckedself.refreshWctx()setButtonText()menu=QMenu()forstatinStatusType.preferredOrder:val=statusTypes[stat]a=menu.addAction('%s%s'%(stat,val.name))a.setCheckable(True)a.setChecked(self.opts[val.name])a.triggered.connect(statusTypeTrigger)pb.setMenu(menu)setButtonText()pb.storeref=menuself.tv=tvself.le=le# Diff panel side of splittervbox=QVBoxLayout()vbox.setMargin(0)docf=QFrame(split)sp=SP(SP.Expanding,SP.Expanding)sp.setHorizontalStretch(1)sp.setVerticalStretch(0)docf.setSizePolicy(sp)docf.setLayout(vbox)self.docf=docfhbox=QHBoxLayout()hbox.setContentsMargins(5,0,0,0)self.fnamelabel=QLabel()self.fnamelabel.setContextMenuPolicy(Qt.CustomContextMenu)self.connect(self.fnamelabel,SIGNAL('customContextMenuRequested(const QPoint &)'),self.customContextMenuRequested)hbox.addWidget(self.fnamelabel)hbox.addStretch()self.override=QCheckBox()self.override.setText(_('Show Contents'))self.override.setCheckable(True)self.override.setEnabled(False)self.override.toggled.connect(self.refreshDiff)hbox.addWidget(self.override)hbox.addSpacing(6)self.te=QTextEdit()self.te.document().setDefaultStyleSheet(qtlib.thgstylesheet)self.te.setReadOnly(True)self.te.setLineWrapMode(QTextEdit.NoWrap)vbox.addLayout(hbox)vbox.addWidget(self.te,1)self.split=splitself.diffvbox=vboxQTimer.singleShot(0,self.refreshWctx)defgetTitle(self):ifself.pats:returnhglib.tounicode(_('%s - status (selection filtered)')%hglib.get_reponame(self.repo))else:returnhglib.tounicode(_('%s - status')%hglib.get_reponame(self.repo))defrestoreState(self,data):returnself.split.restoreState(data)defsaveState(self):returnself.split.saveState()defcustomContextMenuRequested(self,point):'menu request for filename label'ifself.curRowisNone:returnpoint=self.fnamelabel.mapToGlobal(point)path,status,mst,u=self.curRowselrows=[(set(status+mst.lower()),path),]action=wctxactions.wctxactions(self,point,self.repo,selrows)ifaction:self.emit(SIGNAL('menuAction()'))defkeyPressEvent(self,event):ifevent.key()==Qt.Key_F5:self.te.clear()self.refreshWctx()else:returnsuper(StatusWidget,self).keyPressEvent(event)defrefreshWctx(self):ifself.refreshing: return
self.te.clear()
self.fnamelabel.clear()
+ self.curRow = None+ self.override.setChecked(False)+ self.override.setEnabled(False) self.emit(SIGNAL('loadBegin()'))
self.refreshing = StatusThread(self.repo, self.pats, self.opts)
self.connect(self.refreshing, SIGNAL('finished'), self.reloadComplete)
# re-emit error messages from this objectself.connect(self.refreshing,SIGNAL('errorMessage'),lambdamsg:self.emit(SIGNAL('errorMessage'),msg))self.refreshing.start()defreloadComplete(self,wctx,patchecked):self.ms=merge.mergestate(self.repo)self.wctx=wctxself.patchecked=patchecked.copy()self.updateModel()self.emit(SIGNAL('loadComplete()'))self.refreshing.wait()self.refreshing=NonedefupdateModel(self):self.tv.setSortingEnabled(False)ifself.tv.model():checked=self.tv.model().getChecked()else:checked=self.patcheckedifself.patsandnotself.patchecked:qtlib.WarningMsgBox(_('No appropriate files'),_('No files found for this operation'),parent=self)tm=WctxModel(self.wctx,self.ms,self.opts,checked)self.tv.setModel(tm)self.tv.setSortingEnabled(True)self.tv.setColumnHidden(COL_PATH,self.isMerge())self.tv.setColumnHidden(COL_MERGE_STATE,nottm.anyMerge())forcolinxrange(COL_SIZE):self.tv.resizeColumnToContents(col)self.connect(self.tv,SIGNAL('activated(QModelIndex)'),tm.toggleRow)self.connect(self.tv,SIGNAL('pressed(QModelIndex)'),tm.pressedRow)self.connect(self.le,SIGNAL('textEdited(QString)'),tm.setFilter)self.connect(tm,SIGNAL('checkToggled()'),self.updateCheckCount)self.updateCheckCount()defupdateCheckCount(self):text=_('Checkmarked file count: %d')%len(self.getChecked())self.countlbl.setText(text)defisMerge(self):returnbool(self.wctx.p2())defgetChecked(self,types=None):model=self.tv.model()ifmodel:checked=model.getChecked()iftypesisNone:return[fforf,vinchecked.iteritems()ifv]else:files=[]forrowinmodel.getAllRows():path,status,mst,upath,ext,sz=rowifstatusintypesandchecked[path]:files.append(path)returnfileselse:return[]defrowSelected(self,index):'Connected to treeview "clicked" signal'self.curRow=Noneself.override.setChecked(False)self.curRow=index.model().getRow(index)self.refreshDiff()defrefreshDiff(self):ifself.curRowisNone:returnpath,status,mst,upath,ext,sz=self.curRowwfile=util.pconvert(path)self.fnamelabel.setText(statusMessage(status,mst,upath))showanyway=self.override.isChecked()hu=htmlui.htmlui()ifstatusin'?I':ifshowanyway:# Read untracked file contents from working directorydiff=open(self.repo.wjoin(wfile),'r').read()if'\0'indiff:diff=_('<b>Contents are binary, not previewable</b>')self.te.setHtml(diff)else:self.te.setText(diff)else:diff=_('<b>Not displayed</b>')self.te.setHtml(diff)self.override.setEnabled(True)returnelifstatusin'!C':ifshowanyway:# Read file contents from parent revisionctx=self.repo['.']diff=ctx.filectx(wfile).data()if'\0'indiff:diff=_('<b>Contents are binary, not previewable</b>')self.te.setHtml(diff)else:self.te.setText(diff)else:diff=_('<b>Not displayed</b>')self.te.setHtml(diff)self.override.setEnabled(True)returnelifstatusin'S':ifshowanyway:try:sroot=self.repo.wjoin(path)srepo=hg.repository(hu,path=sroot)srev=self.wctx.substate.get(path,subrepo.nullstate)[1]sactual=srepo['.'].hex()commands.status(hu,srepo)out=[hu.getdata()[0]]ifsrev!=sactual:out.append('<b>')out.append(_('revision changed from:'))out.append('</b><br>')opts={'date':None,'user':None,'rev':[srev]}commands.log(hu,srepo,**opts)out.append(hu.getdata()[0])out.append('<b>')out.append(_('to:'))out.append('</b><br>')opts['rev']=[sactual]commands.log(hu,srepo,**opts)out.append(hu.getdata()[0])diff=''.join(out)excepterror.RepoError:diff=_('<b>Not an hg subrepo, not previewable</b>')else:diff=_('<b>Subrepository status not displayed</b>')self.te.setHtml(diff)self.override.setEnabled(True)returnwarnings=chunkselect.check_max_diff(self.wctx,wfile)ifwarningsandnotshowanyway:text='<b>Diffs not displayed: %s</b>'%warnings[1]self.te.setHtml(text)self.override.setEnabled(True)returnself.override.setEnabled(False)self.override.setChecked(True)# Generate diffs to first parentm=cmdutil.matchfiles(self.repo,[wfile])try:fors,linqtlib.difflabel(self.wctx.diff,match=m,git=True):hu.write(s,label=l)except(IOError,error.RepoError,error.LookupError,util.Abort),e:err=hglib.tounicode(str(e))self.emit(SIGNAL('errorMessage'),QString(err))returno,e=hu.getdata()diff=oor_('<em>No displayable differences</em>')ifself.isMerge():header=_('===== Diff to first parent %d:%s =====\n')%(self.wctx.p1().rev(),str(self.wctx.p1()))header='<h3>'+header+'</h3></br>'text=header+diffelse:self.te.setHtml(diff)return# Generate diffs to second parenttry:fors,linqtlib.difflabel(self.wctx.diff,self.wctx.p2(),match=m,git=True):hu.write(s,label=l)except(IOError,error.RepoError,error.LookupError,util.Abort),e:err=hglib.tounicode(str(e))self.emit(SIGNAL('errorMessage'),QString(err))returntext+='</br><h3>'text+=_('===== Diff to second parent %d:%s =====\n')%(self.wctx.p2().rev(),str(self.wctx.p2()))text+='</h3></br>'o,e=hu.getdata()diff=oor_('<em>No displayable differences</em>')self.te.setHtml(text+diff)classStatusThread(QThread):'''Background thread for generating a workingctx'''def__init__(self,repo,pats,opts,parent=None):super(StatusThread,self).__init__()self.repo=repoself.pats=patsself.opts=optsdefrun(self):hglib.invalidaterepo(self.repo)extract=lambdax,y:dict(zip(x,map(y.get,x)))stopts=extract(('unknown','ignored','clean'),self.opts)patchecked={}try:ifself.pats:m=cmdutil.match(self.repo,self.pats)status=self.repo.status(match=m,**stopts)# Record all matched files as initially checkedfori,statinenumerate(StatusType.preferredOrder):ifstat=='S':continueval=statusTypes[stat]ifself.opts[val.name]:d=dict([(fn,True)forfninstatus[i]])patchecked.update(d)wctx=context.workingctx(self.repo,changes=status)else:wctx=self.repo[None]wctx.status(**stopts)except(OSError,IOError,util.Abort),e:err=hglib.tounicode(str(e))self.emit(SIGNAL('errorMessage'),QString(err))try:wctx.dirtySubrepos=[]forsinwctx.substate:ifwctx.sub(s).dirty():wctx.dirtySubrepos.append(s)except(OSError,IOError,util.Abort,error.RepoLookupError,error.ConfigError),e:err=hglib.tounicode(str(e))self.emit(SIGNAL('errorMessage'),QString(err))self.emit(SIGNAL('finished'),wctx,patchecked)classWctxFileTree(QTreeView):def__init__(self,repo,parent=None):QTreeView.__init__(self,parent)self.repo=repoself.setSelectionMode(QTreeView.ExtendedSelection)self.setContextMenuPolicy(Qt.CustomContextMenu)self.connect(self,SIGNAL('customContextMenuRequested(const QPoint &)'),self.customContextMenuRequested)defkeyPressEvent(self,event):ifevent.key()==32:forindexinself.selectedRows():self.model().toggleRow(index)ifevent.key()==Qt.Key_Dandevent.modifiers()==Qt.ControlModifier:selfiles=[]forindexinself.selectedRows():selfiles.append(self.model().getRow(index)[COL_PATH])visdiff.visualdiff(self.repo.ui,self.repo,selfiles,{})else:returnsuper(WctxFileTree,self).keyPressEvent(event)defdragObject(self):urls=[]forindexinself.selectedRows():path=self.model().getRow(index)[COL_PATH]u=QUrl()u.setPath('file://'+os.path.join(self.repo.root,path))urls.append(u)ifurls:d=QDrag(self)m=QMimeData()m.setUrls(urls)d.setMimeData(m)d.start(Qt.CopyAction)defmousePressEvent(self,event):self.pressPos=event.pos()self.pressTime=QTime.currentTime()returnQTreeView.mousePressEvent(self,event)defmouseMoveEvent(self,event):d=event.pos()-self.pressPosifd.manhattanLength()<QApplication.startDragDistance():returnQTreeView.mouseMoveEvent(self,event)elapsed=self.pressTime.msecsTo(QTime.currentTime())ifelapsed<QApplication.startDragTime():returnQTreeView.mouseMoveEvent(self,event)self.dragObject()returnQTreeView.mouseMoveEvent(self,event)defcustomContextMenuRequested(self,point):selrows=[]forindexinself.selectedRows():path,status,mst,u,ext,sz=self.model().getRow(index)selrows.append((set(status+mst.lower()),path))point=self.mapToGlobal(point)action=wctxactions.wctxactions(self,point,self.repo,selrows)ifaction:self.emit(SIGNAL('menuAction()'))defselectedRows(self):returnself.selectionModel().selectedRows()classWctxModel(QAbstractTableModel):def__init__(self,wctx,ms,opts,checked,parent=None):QAbstractTableModel.__init__(self,parent)rows=[]defmkrow(fname,st):ext,sizek='',''try:mst=fnameinmsandms[fname].upper()or""name,ext=os.path.splitext(fname)sizebytes=wctx[fname].size()sizek=(sizebytes+1023)//1024exceptEnvironmentError:passreturn[fname,st,mst,hglib.tounicode(fname),ext[1:],sizek]ifopts['modified']:forminwctx.modified():checked[m]=checked.get(m,True)rows.append(mkrow(m,'M'))ifopts['added']:forainwctx.added():checked[a]=checked.get(a,True)rows.append(mkrow(a,'A'))ifopts['removed']:forrinwctx.removed():mst=rinmsandms[r].upper()or""checked[r]=checked.get(r,True)rows.append(mkrow(r,'R'))ifopts['deleted']:fordinwctx.deleted():mst=dinmsandms[d].upper()or""checked[d]=checked.get(d,False)rows.append(mkrow(d,'!'))ifopts['unknown']:foruinwctx.unknown():checked[u]=checked.get(u,False)rows.append(mkrow(u,'?'))ifopts['ignored']:foriinwctx.ignored():checked[i]=checked.get(i,False)rows.append(mkrow(i,'I'))ifopts['clean']:forcinwctx.clean():checked[c]=checked.get(c,False)rows.append(mkrow(c,'C'))ifopts['subrepo']:forsinwctx.dirtySubrepos:checked[s]=checked.get(s,False)rows.append(mkrow(s,'S'))self.headers=('*',_('Stat'),_('M'),_('Filename'),_('Type'),_('Size (KB)'))self.checked=checkedself.unfiltered=rowsself.rows=rowsdefrowCount(self,parent):returnlen(self.rows)defcolumnCount(self,parent):returnlen(self.headers)defdata(self,index,role):ifnotindex.isValid():returnQVariant()path,status,mst,upath,ext,sz=self.rows[index.row()]ifindex.column()==COL_PATH:ifrole==Qt.CheckStateRole:# also Qt.PartiallyCheckedifself.checked[path]:returnQt.Checkedelse:returnQt.Uncheckedelifrole==Qt.DisplayRole:returnQVariant(self.rows[index.row()][index.column()])elifrole==Qt.TextColorRole:ifmst:return_colors.get(mst.lower(),QColor('black'))else:return_colors.get(status,QColor('black'))elifrole==Qt.ToolTipRole:returnQVariant(statusMessage(status,mst,upath))''' elif role == Qt.DecorationRole and index.column() == COL_STATUS: if status in statusTypes: ico = QIcon() ico.addPixmap(QPixmap('icons/' + statusTypes[status].icon)) return QVariant(ico) '''returnQVariant()defheaderData(self,col,orientation,role):ifrole!=Qt.DisplayRoleororientation!=Qt.Horizontal:returnQVariant()else:returnQVariant(self.headers[col])defflags(self,index):flags=Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsDragEnabledifindex.column()==COL_PATH:flags|=Qt.ItemIsUserCheckablereturnflags# Custom methodsdefanyMerge(self):forrinself.rows:ifr[COL_MERGE_STATE]:returnTruereturnFalsedefgetRow(self,index):assertindex.isValid()returnself.rows[index.row()]defgetAllRows(self):forrowinself.rows:yieldrowdeftoggleRow(self,index):'Connected to "activated" signal, emitted by dbl-click or enter'assertindex.isValid()fname=self.rows[index.row()][COL_PATH]self.emit(SIGNAL("layoutAboutToBeChanged()"))self.checked[fname]=notself.checked[fname]self.emit(SIGNAL("layoutChanged()"))self.emit(SIGNAL("checkToggled()"))defpressedRow(self,index):'Connected to "pressed" signal, emitted by mouse clicks'assertindex.isValid()ifindex.column()==COL_PATH:self.toggleRow(index)defsort(self,col,order):self.emit(SIGNAL("layoutAboutToBeChanged()"))ifcol==COL_PATH:c=self.checkedself.rows.sort(lambdax,y:cmp(c[x[col]],c[y[col]]))else:self.rows.sort(lambdax,y:cmp(x[col],y[col]))iforder==Qt.DescendingOrder:self.rows.reverse()self.emit(SIGNAL("layoutChanged()"))self.reset()defsetFilter(self,match):'simple match in filename filter'self.emit(SIGNAL("layoutAboutToBeChanged()"))self.rows=[rforrinself.unfilteredifmatchinr[COL_PATH_DISPLAY]]self.emit(SIGNAL("layoutChanged()"))self.reset()defgetChecked(self):returnself.checked.copy()defstatusMessage(status,mst,upath):tip=''ifstatusinstatusTypes:upath="<span style='font-family:Courier'>%s </span>"%upathtip=statusTypes[status].desc%upathifmst=='R':tip+=_(', resolved merge')elifmst=='U':tip+=_(', unresolved merge')returntipclassStatusType(object):preferredOrder='MAR?!ICS'def__init__(self,name,icon,desc,uilabel):self.name=nameself.icon=iconself.desc=descself.uilabel=uilabelstatusTypes={'M':StatusType('modified','menucommit.ico',_('%s is modified'),'status.modified'),'A':StatusType('added','fileadd.ico',_('%s is added'),'status.added'),'R':StatusType('removed','filedelete.ico',_('%s is removed'),'status.removed'),'?':StatusType('unknown','shelve.ico',_('%s is not tracked (unknown)'),'status.unknown'),'!':StatusType('deleted','menudelete.ico',_('%s is missing!'),'status.deleted'),'I':StatusType('ignored','ignore.ico',_('%s is ignored'),'status.ignored'),'C':StatusType('clean','',_('%s is not modified (clean)'),'status.clean'),'S':StatusType('subrepo','hg.ico',_('%s is a dirty subrepo'),'status.subrepo'),}classStatusDialog(QDialog):'Standalone status browser'def__init__(self,pats,opts,parent=None):QDialog.__init__(self,parent)layout=QVBoxLayout()layout.setContentsMargins(0,6,0,0)self.setLayout(layout)self.stwidget=StatusWidget(pats,opts,None,self)layout.addWidget(self.stwidget,1)self.stbar=QStatusBar(self)layout.addWidget(self.stbar)s=QSettings()self.stwidget.restoreState(s.value('status/state').toByteArray())self.restoreGeometry(s.value('status/geom').toByteArray())self.connect(self.stwidget,SIGNAL('titleTextChanged'),self.setTitle)self.connect(self.stwidget,SIGNAL('errorMessage'),self.errorMessage)self.setTitle(self.stwidget.getTitle())defsetTitle(self,title):self.setWindowTitle(title)deferrorMessage(self,msg):self.stbar.showMessage(msg)defaccept(self):s=QSettings()s.setValue('status/state',self.stwidget.saveState())s.setValue('status/geom',self.saveGeometry())QDialog.accept(self)defreject(self):s=QSettings()s.setValue('status/state',self.stwidget.saveState())s.setValue('status/geom',self.saveGeometry())QDialog.reject(self)defrun(ui,*pats,**opts):returnStatusDialog(pats,opts)
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.