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.
stable
cmdui: show red error message if command fails
This was especially an issue when using the email option under synchronize. If the user typed their password incorrectly, the program would simply display the message "Finished" without any indication that it had failed unless the user then clicked the details button.
# cmdui.py - A widget to execute Mercurial command for TortoiseHg## Copyright 2010 Yuki KODAMA <endflow.net@gmail.com>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference.importos,glob,shlex,sys,timefromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*fromPyQt4.QsciimportQsciScintillafrommercurialimportutilfromtortoisehg.utilimporthglib,pathsfromtortoisehg.hgqt.i18nimport_,localgettextfromtortoisehg.hgqtimportqtlib,qscilib,threadlocal=localgettext()defstartProgress(topic,status):topic,item,pos,total,unit=topic,'...',status,None,''return(topic,pos,item,unit,total)defstopProgress(topic):topic,item,pos,total,unit=topic,'',None,None,''return(topic,pos,item,unit,total)classProgressMonitor(QWidget):'Progress bar for use in workbench status bar'def__init__(self,topic,parent):super(ProgressMonitor,self).__init__(parent=parent)hbox=QHBoxLayout()hbox.setContentsMargins(*(0,)*4)self.setLayout(hbox)self.idle=Falseself.pbar=QProgressBar()self.pbar.setTextVisible(False)self.pbar.setMinimum(0)hbox.addWidget(self.pbar)self.topic=QLabel(topic)hbox.addWidget(self.topic,0)self.status=QLabel()hbox.addWidget(self.status,1)self.pbar.setMaximum(100)self.pbar.reset()self.status.setText('')defclear(self):self.pbar.setMinimum(0)self.pbar.setMaximum(100)self.pbar.setValue(100)self.status.setText('')self.idle=Truedefsetcounts(self,cur,max):self.pbar.setMaximum(max)self.pbar.setValue(cur)defunknown(self):self.pbar.setMinimum(0)self.pbar.setMaximum(0)classThgStatusBar(QStatusBar):linkActivated=pyqtSignal(QString)def__init__(self,parent=None):QStatusBar.__init__(self,parent=parent)self.topics={}self.lbl=QLabel()self.lbl.linkActivated.connect(self.linkActivated)self.addWidget(self.lbl) self.setStyleSheet('QStatusBar::item { border: none }')
@pyqtSlot(unicode)
- def showMessage(self, ustr):
+ def showMessage(self, ustr, error=False):
self.lbl.setText(ustr)
+ if error:+ self.lbl.setStyleSheet('QLabel { color: red }')+ else:+ self.lbl.setStyleSheet('') def clear(self):
keys = self.topics.keys()
forkeyinkeys:pm=self.topics[key]self.removeWidget(pm)delself.topics[key]@pyqtSlot(QString,object,QString,QString,object)defprogress(self,topic,pos,item,unit,total,root=None):'Progress signal received from repowidget'# topic is current operation# pos is the current numeric position (revision, bytes)# item is a non-numeric marker of current position (current file)# unit is a string label# total is the highest expected pos## All topics should be marked closed by setting pos to Noneifroot:key=(root,topic)else:key=topicifposisNoneor(notposandnottotal):ifkeyinself.topics:pm=self.topics[key]self.removeWidget(pm)delself.topics[key]returnifkeynotinself.topics:pm=ProgressMonitor(topic,self)pm.setMaximumHeight(self.lbl.sizeHint().height())self.addWidget(pm)self.topics[key]=pmelse:pm=self.topics[key]iftotal:fmt='%s / %s '%(unicode(pos),unicode(total))ifunit:fmt+=unitpm.status.setText(fmt)pm.setcounts(pos,total)else:ifitem:item=item[-30:]pm.status.setText('%s%s'%(unicode(pos),item))pm.unknown()classCore(QObject):"""Core functionality for running Mercurial command. Do not attempt to instantiate and use this directly. """commandStarted=pyqtSignal()commandFinished=pyqtSignal(int)commandCanceling=pyqtSignal()output=pyqtSignal(QString,QString)progress=pyqtSignal(QString,object,QString,QString,object)def__init__(self,logWindow,parent):super(Core,self).__init__(parent)self.thread=Noneself.extproc=Noneself.stbar=Noneself.queue=[]self.rawoutlines=[]self.display=Noneself.useproc=FalseiflogWindow:self.outputLog=LogWidget()self.outputLog.installEventFilter(qscilib.KeyPressInterceptor(self))self.output.connect(self.outputLog.appendLog)### Public Methods ###defrun(self,cmdline,*cmdlines,**opts):'''Execute or queue Mercurial command'''self.display=opts.get('display')self.useproc=opts.get('useproc',False)self.queue.append(cmdline)iflen(cmdlines):self.queue.extend(cmdlines)ifself.useproc:self.runproc()elifnotself.running():self.runNext()defcancel(self):'''Cancel running Mercurial command'''ifself.running():try:ifself.extproc:self.extproc.close()elifself.thread:self.thread.abort()exceptAttributeError:passself.commandCanceling.emit()defsetStbar(self,stbar):self.stbar=stbardefrunning(self):try:ifself.extproc:returnself.extproc.state()!=QProcess.NotRunningelifself.thread:returnself.thread.isRunning()exceptAttributeError:passreturnFalsedefrawoutput(self):return''.join(self.rawoutlines)### Private Method ###defrunproc(self):'Run mercurial command in separate process'exepath=Noneifhasattr(sys,'frozen'):progdir=paths.get_prog_root()exe=os.path.join(progdir,'hg.exe')ifos.path.exists(exe):exepath=exeifnotexepath:exepath=paths.find_in_path('hg')defstart(cmdline,display):self.rawoutlines=[]ifdisplay:cmd='%% hg %s\n'%displayelse:cmd='%% hg %s\n'%' '.join(cmdline)self.output.emit(cmd,'control')proc.start(exepath,cmdline,QIODevice.ReadOnly)@pyqtSlot(int)deffinished(ret):ifret:msg=_('[command returned code %d%%s]')%int(ret)else:msg=_('[command completed successfully %s]')msg=msg%time.asctime()+'\n'self.output.emit(msg,'control')ifret==0andself.queue:start(self.queue.pop(0),'')else:self.queue=[]self.extproc=Noneself.commandFinished.emit(ret)defhandleerror(error):iferror==QProcess.FailedToStart:self.output.emit(_('failed to start command\n'),'ui.error')finished(-1)eliferror!=QProcess.Crashed:self.output.emit(_('error while running command\n'),'ui.error')defstdout():data=proc.readAllStandardOutput().data()self.rawoutlines.append(data)self.output.emit(hglib.tounicode(data),'')defstderr():data=proc.readAllStandardError().data()self.output.emit(hglib.tounicode(data),'ui.error')self.extproc=proc=QProcess(self)proc.started.connect(self.onCommandStarted)proc.finished.connect(finished)proc.readyReadStandardOutput.connect(stdout)proc.readyReadStandardError.connect(stderr)proc.error.connect(handleerror)start(self.queue.pop(0),self.display)defrunNext(self):ifnotself.queue:returnFalsecmdline=self.queue.pop(0)self.thread=thread.CmdThread(cmdline,self.display,self.parent())self.thread.started.connect(self.onCommandStarted)self.thread.commandFinished.connect(self.onThreadFinished)self.thread.outputReceived.connect(self.output)self.thread.progressReceived.connect(self.progress)ifself.stbar:self.thread.progressReceived.connect(self.stbar.progress)self.thread.start()returnTruedefclearOutput(self):ifhasattr(self,'outputLog'):self.outputLog.clear()### Signal Handlers ###@pyqtSlot()defonCommandStarted(self):ifself.stbar:self.stbar.showMessage(_('Running...'))self.commandStarted.emit() @pyqtSlot(int)
def onThreadFinished(self, ret):
if self.stbar:
+ error = False if ret is None:
self.stbar.clear()
if self.thread.abortbyuser:
status = _('Terminated by user')
else:
status = _('Terminated')
+ elif ret == 0:+ status = _('Finished') else:
- status = _('Finished')
- self.stbar.showMessage(status)
+ status = _('Failed!')
+ error = True+ self.stbar.showMessage(status, error)
self.display = None
if ret == 0 and self.runNext():
return# run next commandelse:self.queue=[]text=self.thread.rawoutput.join('')self.rawoutlines=[hglib.fromunicode(text,'replace')]self.commandFinished.emit(ret)classLogWidget(QsciScintilla):"""Output log viewer"""def__init__(self,parent=None):super(LogWidget,self).__init__(parent)self.setReadOnly(True)self.setUtf8(True)self.setMarginWidth(1,0)self.setWrapMode(QsciScintilla.WrapCharacter)self._initfont()self._initmarkers()def_initfont(self):tf=qtlib.getfont('fontoutputlog')tf.changed.connect(self.forwardFont)self.setFont(tf.font())@pyqtSlot(QFont)defforwardFont(self,font):self.setFont(font)def_initmarkers(self):self._markers={}forlin('ui.error','control'):self._markers[l]=m=self.markerDefine(QsciScintilla.Background)c=QColor(qtlib.getbgcoloreffect(l))ifc.isValid():self.setMarkerBackgroundColor(c,m)# NOTE: self.setMarkerForegroundColor() doesn't take effect,# because it's a *Background* marker.@pyqtSlot(unicode,str)defappendLog(self,msg,label):"""Append log text to the last line; scrolls down to there"""self.append(msg)self._setmarker(xrange(self.lines()-unicode(msg).count('\n')-1,self.lines()-1),label)self.setCursorPosition(self.lines()-1,0)def_setmarker(self,lines,label):forminself._markersforlabel(label):foriinlines:self.markerAdd(i,m)def_markersforlabel(self,label):returniter(self._markers[l]forlinstr(label).split()iflinself._markers)class_LogWidgetForConsole(LogWidget):"""Wrapped LogWidget for ConsoleWidget"""returnPressed=pyqtSignal(unicode)"""Return key pressed when cursor is on prompt line"""_prompt='% 'def__init__(self,parent=None):super(_LogWidgetForConsole,self).__init__(parent)self._prompt_marker=self.markerDefine(QsciScintilla.Background)self.setMarkerBackgroundColor(QColor('#e8f3fe'),self._prompt_marker)self.cursorPositionChanged.connect(self._updatePrompt)defkeyPressEvent(self,event):ifevent.key()in(Qt.Key_Return,Qt.Key_Enter):ifself._cursoronpromptline():self.returnPressed.emit(self.commandText())returnsuper(_LogWidgetForConsole,self).keyPressEvent(event)defsetPrompt(self,text):iftext==self._prompt:returnself.clearPrompt()self._prompt=textself.openPrompt()@pyqtSlot()defopenPrompt(self):"""Show prompt line and enable user input"""self.closePrompt()self.markerAdd(self.lines()-1,self._prompt_marker)self.append(self._prompt)self.setCursorPosition(self.lines()-1,len(self._prompt))self.setReadOnly(False)# make sure the prompt line is visible. Because QsciScintilla may# delay line wrapping, setCursorPosition() doesn't always scrolls# to the correct position.# http://www.scintilla.org/ScintillaDoc.html#LineWrappingself.SCN_PAINTED.connect(self._scrollCaretOnPainted)@pyqtSlot()def_scrollCaretOnPainted(self):self.SCN_PAINTED.disconnect(self._scrollCaretOnPainted)self.SendScintilla(self.SCI_SCROLLCARET)@pyqtSlot()defclosePrompt(self):"""Disable user input"""ifself.commandText():self._setmarker((self.lines()-1,),'control')self.markerDelete(self.lines()-1,self._prompt_marker)self._newline()self.setCursorPosition(self.lines()-1,0)self.setReadOnly(True)@pyqtSlot()defclearPrompt(self):"""Clear prompt line"""line=self.lines()-1ifnot(self.markersAtLine(line)&(1<<self._prompt_marker)):returnself.markerDelete(line)self.setSelection(line,0,line,self.lineLength(line))self.removeSelectedText()@pyqtSlot(int,int)def_updatePrompt(self,line,pos):"""Update availability of user input"""ifself.markersAtLine(line)&(1<<self._prompt_marker):self.setReadOnly(False)self._ensurePrompt(line)else:self.setReadOnly(True)def_ensurePrompt(self,line):"""Insert prompt string if not available"""s=unicode(self.text(line))ifs.startswith(self._prompt):returnfori,cinenumerate(self._prompt):ifs[i:i+1]!=c:self.insertAt(self._prompt[i:],line,i)breakself.setCursorPosition(line,self.lineLength(line))defcommandText(self):"""Return the current command text"""l=self.lines()-1ifself.markersAtLine(l)&(1<<self._prompt_marker):returnself.text(l)[len(self._prompt):]else:return''def_newline(self):ifself.lineLength(self.lines()-1)>0:self.append('\n')def_cursoronpromptline(self):line=self.getCursorPosition()[0]returnself.markersAtLine(line)&(1<<self._prompt_marker)class_ConsoleCmdTable(dict):"""Command table for ConsoleWidget"""_cmdfuncprefix='_cmd_'def__call__(self,func):ifnotfunc.__name__.startswith(self._cmdfuncprefix):raiseValueError('bad command function name %s'%func.__name__)self[func.__name__[len(self._cmdfuncprefix):]]=funcreturnfuncclassConsoleWidget(QWidget):"""Console to run hg/thg command and show output"""closeRequested=pyqtSignal()progressReceived=pyqtSignal(QString,object,QString,QString,object,object)"""Emitted when progress received Args: topic, pos, item, unit, total, reporoot """_cmdtable=_ConsoleCmdTable()# TODO: command history and completiondef__init__(self,parent=None):super(ConsoleWidget,self).__init__(parent)self.setLayout(QVBoxLayout())self.layout().setContentsMargins(0,0,0,0)self._initlogwidget()self.setFocusProxy(self._logwidget)self.setRepository(None)self.openPrompt()def_initlogwidget(self):self._logwidget=_LogWidgetForConsole(self)self._logwidget.returnPressed.connect(self._runcommand)self.layout().addWidget(self._logwidget)# compatibility methods with LogWidgetfornamein('openPrompt','closePrompt','clear'):setattr(self,name,getattr(self._logwidget,name))@util.propertycachedef_cmdcore(self):cmdcore=Core(False,self)cmdcore.output.connect(self._logwidget.appendLog)cmdcore.commandStarted.connect(self.closePrompt)cmdcore.commandFinished.connect(self.openPrompt)cmdcore.progress.connect(self._emitProgress)returncmdcore@util.propertycachedef_extproc(self):extproc=QProcess(self)extproc.started.connect(self.closePrompt)extproc.finished.connect(self.openPrompt)defhandleerror(error):msgmap={QProcess.FailedToStart:_('failed to run command\n'),QProcess.Crashed:_('crashed\n')}ifextproc.state()==QProcess.NotRunning:self._logwidget.closePrompt()self._logwidget.appendLog(msgmap.get(error,_('error while running command\n')),'ui.error')ifextproc.state()==QProcess.NotRunning:self._logwidget.openPrompt()extproc.error.connect(handleerror)defput(bytes,label=None):self._logwidget.appendLog(hglib.tounicode(bytes.data()),label)extproc.readyReadStandardOutput.connect(lambda:put(extproc.readAllStandardOutput()))extproc.readyReadStandardError.connect(lambda:put(extproc.readAllStandardError(),'ui.error'))returnextproc@pyqtSlot(unicode,str)defappendLog(self,msg,label):"""Append log text from another cmdui"""self._logwidget.clearPrompt()try:self._logwidget.appendLog(msg,label)finally:self.openPrompt()@pyqtSlot(object)defsetRepository(self,repo):"""Change the current working repository"""self._repo=repoself._logwidget.setPrompt('%s%% '%(repoandrepo.displaynameor''))@propertydefcwd(self):"""Return the current working directory"""returnself._repoandself._repo.rootoros.getcwd()@pyqtSlot(unicode,object,unicode,unicode,object)def_emitProgress(self,*args):self.progressReceived.emit(*(args+(self._repoandself._repo.rootorNone,)))@pyqtSlot(unicode)def_runcommand(self,cmdline):try:args=list(self._parsecmdline(cmdline))exceptValueError,e:self.closePrompt()self._logwidget.appendLog(unicode(e)+'\n','ui.error')self.openPrompt()returnifnotargs:self.openPrompt()returncmd=args.pop(0)try:self._cmdtable[cmd](self,args)exceptKeyError:returnself._runextcommand(cmdline)def_parsecmdline(self,cmdline):"""Split command line string to imitate a unix shell"""try:args=shlex.split(hglib.fromunicode(cmdline))exceptValueError,e:raiseValueError(_('command parse error: %s')%e)foreinargs:e=util.expandpath(e)ifutil.any(cineforcin'*?[]'):expanded=glob.glob(os.path.join(self.cwd,e))ifnotexpanded:raiseValueError(_('no matches found: %s')%hglib.tounicode(e))forpinexpanded:yieldpelse:yieldedef_runextcommand(self,cmdline):self._extproc.setWorkingDirectory(hglib.tounicode(self.cwd))self._extproc.start(cmdline,QIODevice.ReadOnly)@_cmdtabledef_cmd_hg(self,args):ifself._repo:args=['--cwd',self._repo.root]+argsself._cmdcore.run(args)@_cmdtabledef_cmd_thg(self,args):fromtortoisehg.hgqtimportrunself.closePrompt()try:ifself._repo:args=['-R',self._repo.root]+args# TODO: show errorsrun.dispatch(args)finally:self.openPrompt()@_cmdtabledef_cmd_clear(self,args):self.clear()self.openPrompt()@_cmdtabledef_cmd_cls(self,args):self.clear()self.openPrompt()@_cmdtabledef_cmd_exit(self,args):self.clear()self.openPrompt()self.closeRequested.emit()classWidget(QWidget):"""An embeddable widget for running Mercurial command"""commandStarted=pyqtSignal()commandFinished=pyqtSignal(int)commandCanceling=pyqtSignal()output=pyqtSignal(QString,QString)progress=pyqtSignal(QString,object,QString,QString,object)makeLogVisible=pyqtSignal(bool)def__init__(self,logWindow,statusBar,parent):super(Widget,self).__init__(parent)self.core=Core(logWindow,self)self.core.commandStarted.connect(self.commandStarted)self.core.commandFinished.connect(self.onCommandFinished)self.core.commandCanceling.connect(self.commandCanceling)self.core.output.connect(self.output)self.core.progress.connect(self.progress)ifnotlogWindow:returnvbox=QVBoxLayout()vbox.setSpacing(4)vbox.setContentsMargins(*(1,)*4)self.setLayout(vbox)# command output areaself.core.outputLog.setHidden(True)self.layout().addWidget(self.core.outputLog,1)ifstatusBar:## status and progress labelsself.stbar=ThgStatusBar()self.stbar.setSizeGripEnabled(False)self.core.setStbar(self.stbar)self.layout().addWidget(self.stbar)### Public Methods ###defrun(self,cmdline,*args,**opts):self.core.run(cmdline,*args,**opts)defcancel(self):self.core.cancel()defsetShowOutput(self,visible):ifhasattr(self.core,'outputLog'):self.core.outputLog.setShown(visible)defoutputShown(self):ifhasattr(self.core,'outputLog'):returnself.core.outputLog.isVisible()else:returnFalse### Signal Handler ###@pyqtSlot(int)defonCommandFinished(self,ret):ifret==-1:self.makeLogVisible.emit(True)self.setShowOutput(True)self.commandFinished.emit(ret)classDialog(QDialog):"""A dialog for running random Mercurial command"""def__init__(self,cmdline,parent=None):super(Dialog,self).__init__(parent)self.setWindowFlags(self.windowFlags()&~Qt.WindowContextHelpButtonHint)self.core=Core(True,self)self.core.commandFinished.connect(self.onCommandFinished)vbox=QVBoxLayout()vbox.setSpacing(4)vbox.setContentsMargins(5,5,5,5)# command output areavbox.addWidget(self.core.outputLog,1)## status and progress labelsself.stbar=ThgStatusBar()self.stbar.setSizeGripEnabled(False)self.core.setStbar(self.stbar)vbox.addWidget(self.stbar)# bottom buttonsbuttons=QDialogButtonBox()self.cancelBtn=buttons.addButton(QDialogButtonBox.Cancel)self.cancelBtn.clicked.connect(self.core.cancel)self.core.commandCanceling.connect(self.commandCanceling)self.closeBtn=buttons.addButton(QDialogButtonBox.Close)self.closeBtn.setHidden(True)self.closeBtn.clicked.connect(self.reject)self.detailBtn=buttons.addButton(_('Detail'),QDialogButtonBox.ResetRole)self.detailBtn.setAutoDefault(False)self.detailBtn.setCheckable(True)self.detailBtn.setChecked(True)self.detailBtn.toggled.connect(self.setShowOutput)vbox.addWidget(buttons)self.setLayout(vbox)self.setWindowTitle(_('TortoiseHg Command Dialog'))self.resize(540,420)# start commandself.core.run(cmdline)defsetShowOutput(self,visible):"""show/hide command output"""self.core.outputLog.setVisible(visible)self.detailBtn.setChecked(visible)# workaround to adjust only window heightself.setMinimumWidth(self.width())self.adjustSize()self.setMinimumWidth(0)### Private Method ###defreject(self):ifself.core.running():ret=QMessageBox.question(self,_('Confirm Exit'),_('Mercurial command is still running.\n''Are you sure you want to terminate?'),QMessageBox.Yes|QMessageBox.No,QMessageBox.No)ifret==QMessageBox.Yes:self.core.cancel()# don't close dialogreturn# close dialogifself.returnCode==0:self.accept()# means command successfully finishedelse:super(Dialog,self).reject()@pyqtSlot()defcommandCanceling(self):self.cancelBtn.setDisabled(True)@pyqtSlot(int)defonCommandFinished(self,ret):self.returnCode=retself.cancelBtn.setHidden(True)self.closeBtn.setShown(True)self.closeBtn.setFocus()classRunner(QObject):"""A component for running Mercurial command without UI This command runner doesn't show any UI element unless it gets a warning or an error while the command is running. Once an error or a warning is received, it pops-up a small dialog which contains the command log. """commandStarted=pyqtSignal()commandFinished=pyqtSignal(int)commandCanceling=pyqtSignal()output=pyqtSignal(QString,QString)progress=pyqtSignal(QString,object,QString,QString,object)makeLogVisible=pyqtSignal(bool)def__init__(self,logWindow,parent):super(Runner,self).__init__(parent)self.title=_('TortoiseHg')self.core=Core(logWindow,parent)self.core.commandStarted.connect(self.commandStarted)self.core.commandFinished.connect(self.onCommandFinished)self.core.commandCanceling.connect(self.commandCanceling)self.core.output.connect(self.output)self.core.progress.connect(self.progress)### Public Methods ###defsetTitle(self,title):self.title=titledefrun(self,cmdline,*args,**opts):self.core.run(cmdline,*args,**opts)defrunning(self):returnself.core.running()defcancel(self):self.core.cancel()defoutputShown(self):ifhasattr(self,'dlg'):returnself.dlg.isVisible()else:returnFalsedefsetShowOutput(self,visible=True):ifnothasattr(self.core,'outputLog'):returnifnothasattr(self,'dlg'):self.dlg=dlg=QDialog(self.parent())dlg.setWindowTitle(self.title)dlg.setWindowFlags(Qt.Dialog)dlg.setLayout(QVBoxLayout())dlg.layout().addWidget(self.core.outputLog)self.core.outputLog.setMinimumSize(460,320)self.dlg.setVisible(visible)### Signal Handler ###@pyqtSlot(int)defonCommandFinished(self,ret):ifret!=0:self.makeLogVisible.emit(True)self.setShowOutput(True)self.commandFinished.emit(ret)
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.