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.
# hgcmd.py - A simple dialog to execute random command for TortoiseHg## Copyright 2007 TK Soh <teekaysoh@gmail.com># Copyright 2007 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.importgtkimportgobjectimportpangoimportosimportsysimportthreadingimportQueuefromtortoisehg.util.i18nimport_fromtortoisehg.utilimportshlib,hglibfromtortoisehg.hgtkimportgtklib,hgthreadclassCmdDialog(gtk.Dialog):def__init__(self,cmdline,progressbar=True,text=None):iftype(cmdline)istuple:self.cmdlist=list(cmdline)[1:]cmdline=cmdline[0]else:self.cmdlist=[]title=textor' '.join(cmdline)iflen(title)>80:title=hglib.tounicode(title)[:80]+'...'title=hglib.toutf(title.replace('\n',' '))gtk.Dialog.__init__(self,title=title,flags=gtk.DIALOG_MODAL)self.set_has_separator(False)gtklib.set_tortoise_icon(self,'hg.ico')gtklib.set_tortoise_keys(self)self.cmdline=cmdlineself.returncode=Noneself.hgthread=Noneself.set_default_size(520,400)self._button_stop=gtk.Button(_('Stop'))self._button_stop.connect('clicked',self._on_stop_clicked)self.action_area.pack_start(self._button_stop)self._button_ok=gtk.Button(_('Close'))self._button_ok.connect('clicked',self._on_ok_clicked)self.action_area.pack_start(self._button_ok)self.connect('thg-accept',self._on_ok_clicked)self.connect('delete-event',self._delete)self.connect('response',self._response)self.pbar=Noneifprogressbar:self.last_pbar_update=0hbox=gtk.HBox()self.status_text=gtk.Label()self.status_text.set_text(title)self.status_text.set_alignment(0,0.5)self.status_text.set_ellipsize(pango.ELLIPSIZE_END)hbox.pack_start(self.status_text,True,True,3)# Create a centering alignment objectalign=gtk.Alignment(0.0,0.0,1,0)hbox.pack_end(align,False,False,3)align.show()# create the progress barself.pbar=gtk.ProgressBar()align.add(self.pbar)self.pbar.pulse()self.pbar.show()self.vbox.pack_start(hbox,False,False,3)scrolledwindow=gtk.ScrolledWindow()scrolledwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN)scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)self.textview=gtk.TextView(buffer=None)self.textview.set_editable(False)fontlog=hglib.getfontconfig()['fontlog']self.textview.modify_font(pango.FontDescription(fontlog))scrolledwindow.add(self.textview)self.textbuffer=self.textview.get_buffer()self.textbuffer.create_tag('error',weight=pango.WEIGHT_HEAVY,foreground=gtklib.DRED)self.vbox.pack_start(scrolledwindow,True,True)self.connect('map_event',self._on_window_map_event)self.show_all()def_on_ok_clicked(self,button):""" Ok button clicked handler. """self.response(gtk.RESPONSE_ACCEPT)def_on_stop_clicked(self,button):ifself.hgthread:try:self.hgthread.terminate()exceptValueError:pass# race, thread was already terminateddef_delete(self,widget,event):returnTruedef_response(self,widget,response_id):ifself.hgthreadandself.hgthread.isAlive():widget.emit_stop_by_name('response')def_on_window_map_event(self,event,param):ifself.hgthread:return# Replace stdout file descriptor with our own pipedefpollstdout(*args):whileTrue:# blocking read of stdout pipeo=os.read(self.readfd,1024)ifo: self.stdoutq.put(o)
else:
break
- self.oldstdout = os.dup(sys.__stdout__.fileno()) self.stdoutq = Queue.Queue()
- self.readfd, writefd = os.pipe()
- os.dup2(writefd, sys.__stdout__.fileno())
- thread = threading.Thread(target=pollstdout, args=[])
- thread.start()
+ if os.name == 'nt':+ # Only capture stdout on Windows. This causes hard crashes+ # on some other platforms. See issue #783+ self.readfd, writefd = os.pipe()
+ self.oldstdout = os.dup(sys.__stdout__.fileno())+os.dup2(writefd, sys.__stdout__.fileno())
+thread = threading.Thread(target=pollstdout, args=[])
+thread.start()
self.hgthread = hgthread.HgThread(self.cmdline[1:])
self.hgthread.start()
self._button_ok.set_sensitive(False)self._button_stop.set_sensitive(True)gobject.timeout_add(10,self.process_queue)defwrite(self,msg,append=True):msg=hglib.toutf(msg)ifappend:enditer=self.textbuffer.get_end_iter()self.textbuffer.insert(enditer,msg)else:self.textbuffer.set_text(msg)defprocess_queue(self):""" Handle all the messages currently in the queue (if any). """self.hgthread.process_dialogs()enditer=self.textbuffer.get_end_iter()whileself.hgthread.getqueue().qsize():try:msg=self.hgthread.getqueue().get(0)self.textbuffer.insert(enditer,hglib.toutf(msg))self.textview.scroll_to_mark(self.textbuffer.get_insert(),0)exceptQueue.Empty:passwhileself.hgthread.geterrqueue().qsize():try:msg=hglib.toutf(self.hgthread.geterrqueue().get(0))self.textbuffer.insert_with_tags_by_name(enditer,msg,'error')self.textview.scroll_to_mark(self.textbuffer.get_insert(),0)exceptQueue.Empty:passwhileself.stdoutq.qsize():try:msg=hglib.toutf(self.stdoutq.get(0))self.textbuffer.insert_with_tags_by_name(enditer,msg,'error')self.textview.scroll_to_mark(self.textbuffer.get_insert(),0)exceptQueue.Empty:passself.update_progress()ifnotself.hgthread.isAlive():self.returncode=self.hgthread.return_code()ifself.returncodeisNone:self.write(_('\n[command interrupted]'))elifself.returncode==0andself.cmdlist:cmdline=self.cmdlist.pop(0)text='\n'+hglib.toutf(' '.join(cmdline))+'\n'self.textbuffer.insert(enditer,text)self.textview.scroll_to_mark(self.textbuffer.get_insert(),0)self.hgthread=hgthread.HgThread(cmdline[1:])self.hgthread.start()returnTrue self._button_stop.set_sensitive(False)
self._button_ok.set_sensitive(True)
self._button_ok.grab_focus()
- os.dup2(self.oldstdout, sys.__stdout__.fileno())
- os.close(self.oldstdout)
+ if os.name == 'nt':+ os.dup2(self.oldstdout, sys.__stdout__.fileno())
+os.close(self.oldstdout)
return False # Stop polling this function
else:
return True
defupdate_progress(self):ifnotself.pbar:return# progress bar not enabledifnotself.hgthread.isAlive():self.pbar.unmap()else:# pulse the progress bar every ~100mstm=shlib.get_system_times()[4]iftm-self.last_pbar_update<0.100:returnself.last_pbar_update=tmself.pbar.pulse()defreturn_code(self):ifself.hgthread:returnself.hgthread.return_code()else:returnFalsedefget_buffer(self):start=self.textbuffer.get_start_iter()end=self.textbuffer.get_end_iter()returnself.textbuffer.get_text(start,end)# CmdWidget style constantsSTYLE_NORMAL='normal'# pbar + embedded log viewerSTYLE_COMPACT='compact'# pbar + popup log viewerclassCmdWidget(gtk.VBox):def__init__(self,style=STYLE_NORMAL,tooltips=None,logsize=None):""" style: String. Predefined constans of CmdWidget style. Two styles, STYLE_NORMAL (progress bar + popup log viewer) and STYLE_COMPACT (progress bar + embedded log viewer) are available. Default: STYLE_NORMAL. tooltips: Reference. gtk.Tooltips instance to show tooltips of several buttons. If omitted, a new instance of gtk.Tooltips will be created. Default: None. logsize: Tuple or list containing two numbers. Specify the size of the embedded log viewer. size[0] = width, size[1] = height. If you pass -1 as width or height size, it will be set to the natural size of the widget. Default: tuple(-1, 180). """gtk.VBox.__init__(self)self.hgthread=Noneself.last_pbar_update=0self.useraborted=Falseself.is_normal=style==STYLE_NORMALself.is_compact=style==STYLE_COMPACT# tooltipsiftooltipsisNone:tooltips=gtk.Tooltips()# log viewerifself.is_normal:self.log=CmdLogWidget()iflogsizeisNone:logsize=(-1,180)self.log.set_size_request(logsize[0],logsize[1])self.log.size_request()self.pack_start(self.log)elifself.is_compact:self.dlg=CmdLogDialog()defclose_hook(dialog):self.show_log(False)returnFalseself.dlg.set_close_hook(close_hook)self.log=self.dlg.get_logwidget()else:raise_('unknown CmdWidget style: %s')%style# progress bar boxself.progbox=progbox=gtklib.SlimToolbar(tooltips)self.pack_start(progbox)defadd_button(stock_id,tooltip,handler,toggle=False):btn=progbox.append_button(stock_id,tooltip,toggle)btn.connect('clicked',handler)returnbtn## log toggle buttonself.log_btn=add_button(gtk.STOCK_JUSTIFY_LEFT,_('Toggle log window'),self.log_toggled,toggle=True)## result frameself.rframe=gtk.Frame()progbox.append_widget(self.rframe,expand=True)self.rframe.set_shadow_type(gtk.SHADOW_IN)rhbox=gtk.HBox()self.rframe.add(rhbox)### result labelself.rlabel=gtk.Label()rhbox.pack_end(self.rlabel,True,True,2)self.rlabel.set_alignment(0,0.5)defafter_init():# result iconsself.icons={}defadd_icon(name,stock):img=gtk.Image()rhbox.pack_start(img,False,False,2)img.set_from_stock(stock,gtk.ICON_SIZE_SMALL_TOOLBAR)self.icons[name]=imgadd_icon('succeed',gtk.STOCK_APPLY)add_icon('error',gtk.STOCK_DIALOG_ERROR)# progress barself.pbar=gtk.ProgressBar()progbox.append_widget(self.pbar,expand=True)self.pbar.hide()# stop & close buttonsifself.is_compact:self.stop_btn=add_button(gtk.STOCK_STOP,_('Stop transaction'),self.stop_clicked)self.close_btn=add_button(gtk.STOCK_CLOSE,_('Close this'),self.close_clicked)self.set_buttons(stop=False)ifself.is_normal:self.show_log(False)ifself.is_compact:self.set_pbar(False)gtklib.idle_add_single_call(after_init)### public functions ###defexecute(self,cmdline,callback,*args,**kargs):""" Execute passed command line using 'hgthread'. When the command terminates, the callback function is invoked with its return code. cmdline: command line string. callback: function invoked after the command terminates. def callback(returncode, useraborted, ...) returncode: See the description of 'hgthread'. useraborted: Whether the command was aborted by the user. """ifself.hgthread:return# clear previous logsself.log.clear()# prepare UIself.switch_to(working=True)self.set_buttons(stop=True,close=False)self.already_opened=self.get_pbar()ifnotself.already_opened:defis_done():# show progress bar if it's still workingifself.hgthreadandself.hgthread.isAlive():self.set_pbar(True)returnFalsegobject.timeout_add(500,is_done)# thread startself.hgthread=hgthread.HgThread(cmdline[1:])self.hgthread.start()gobject.timeout_add(10,self.process_queue,callback,args,kargs)defis_alive(self):""" Return whether the thread is alive. """returnself.hgthreadandself.hgthread.isAlive()defstop(self):""" Terminate the thread forcibly. """ifself.hgthread:self.useraborted=Truetry:self.hgthread.terminate()exceptValueError:pass# race, thread was already terminatedself.set_pbar(True)self.set_buttons(stop=False,close=True)defset_result(self,text,style=None):""" Put text message and icon in the progress bar. style: String. If passed 'succeed', 'green' or 'ok', green text and succeed icon will be shown. If passed 'error', 'failed', 'fail' or 'red', red text and error icon will be shown. """markup='<span foreground="%s" weight="%s">%%s</span>'ifstylein('succeed','green','ok'):markup=markup%(gtklib.DGREEN,'bold')icons={'succeed':True}elifstylein('error','failed','fail','red'):markup=markup%(gtklib.DRED,'bold')icons={'error':True}else:markup=markup%('black','normal')icons={}text=gtklib.markup_escape_text(text)self.rlabel.set_markup(markup%text)self.set_icons(**icons)defset_pbar(self,visible):""" Set visible property of the progress bar box. visible: if True, the progress bar box is shown. """ifhasattr(self,'progbox'):self.progbox.set_property('visible',visible)defget_pbar(self):""" Return 'visible' property of the progress bar box. If there is no progress bar, it returns False. """ifhasattr(self,'progbox'):returnself.progbox.get_property('visible')returnFalsedefset_buttons(self,log=None,stop=None,close=None):""" Set visible properties of buttons on the progress bar box. If all arguments are omitted, it does nothing. log: if True, log button is shown. (default: None) stop: if True, stop button is shown. (default: None) close: if True, close button is shown. (default: None) """ifnotlogisNoneandhasattr(self,'log_btn'):self.log_btn.set_property('visible',log)ifnotstopisNoneandhasattr(self,'stop_btn'):self.stop_btn.set_property('visible',stop)ifnotcloseisNoneandhasattr(self,'close_btn'):self.close_btn.set_property('visible',close)defshow_log(self,visible=True):""" Show/hide log viewer. """ifself.is_normal:self.log.set_property('visible',visible)elifself.is_compact:ifvisible:ifself.dlg.get_property('visible'):self.dlg.present()else:self.dlg.show_all()else:self.dlg.hide()else:raise_('invalid state')# change toggle button stateifself.log_btn.get_active()!=visible:self.log_btn.handler_block_by_func(self.log_toggled)self.log_btn.set_active(visible)self.log_btn.handler_unblock_by_func(self.log_toggled)defis_show_log(self):""" Return visible state of log viewer. """ifself.is_normal:returnself.log.get_property('visible')elifself.is_compact:returnself.dlg.get_property('visible')else:raise_('invalid state')### internal use functions ###defupdate_progress(self):ifnotself.pbar:returnifnotself.hgthread.isAlive():self.pbar.set_fraction(0)else:# pulse the progress bar every ~100mstm=shlib.get_system_times()[4]iftm-self.last_pbar_update<0.100:returnself.last_pbar_update=tmself.pbar.pulse()defprocess_queue(self,callback,args,kargs):# process queueself.hgthread.process_dialogs()# output to bufferwhileself.hgthread.getqueue().qsize():try:msg=self.hgthread.getqueue().get(0)self.log.append(hglib.toutf(msg))exceptQueue.Empty:passwhileself.hgthread.geterrqueue().qsize():try:msg=self.hgthread.geterrqueue().get(0)self.log.append(hglib.toutf(msg),error=True)exceptQueue.Empty:pass# update progress barself.update_progress()# check threadifnotself.hgthread.isAlive():returncode=self.hgthread.return_code()ifreturncode==0andnotself.already_opened:self.set_pbar(False)else:self.set_pbar(True)self.switch_to(ready=True)self.set_buttons(stop=False,close=True)ifreturncodeisNone:self.log.append(_('\n[command interrupted]'))ifnotreturncode==0:self.show_log()self.hgthread=Nonedefcall_callback():callback(returncode,self.useraborted,*args,**kargs)self.useraborted=Falsegtklib.idle_add_single_call(call_callback)returnFalse# Stop polling this functionelse:returnTrue# Continue pollingdefset_icons(self,**kargs):forname,imginself.icons.items():visible=kargs.get(name,False)img.set_property('visible',visible)defswitch_to(self,ready=False,working=False):ifready:self.rframe.show()self.pbar.hide()elifworking:self.rframe.hide()self.pbar.show()### signal handlers ###deflog_toggled(self,button):self.show_log(button.get_active())defstop_clicked(self,button):self.stop()defclose_clicked(self,button):self.set_pbar(False)classCmdLogWidget(gtk.VBox):def__init__(self):gtk.VBox.__init__(self)# scrolled panepane=gtk.ScrolledWindow()pane.set_shadow_type(gtk.SHADOW_ETCHED_IN)pane.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)self.add(pane)# log textviewself.textview=gtk.TextView(buffer=None)self.textview.set_editable(False)fontlog=hglib.getfontconfig()['fontlog']self.textview.modify_font(pango.FontDescription(fontlog))pane.add(self.textview)# text bufferself.buffer=self.textview.get_buffer()self.buffer.create_tag('error',weight=pango.WEIGHT_HEAVY,foreground=gtklib.DRED)### public functions ###defappend(self,text,error=False):""" Insert text at the end of the TextView. text: string you want to append. error: if True, append text with 'error' tag. (default: False) """enditer=self.buffer.get_end_iter()iferror:self.buffer.insert_with_tags_by_name(enditer,text,'error')else:self.buffer.insert(enditer,text)self.textview.scroll_to_mark(self.buffer.get_insert(),0)defclear(self):""" Clear all text in the TextView. """self.buffer.delete(self.buffer.get_start_iter(),self.buffer.get_end_iter())classCmdLogDialog(gtk.Window):def__init__(self,title=_('Command Log')):gtk.Window.__init__(self,type=gtk.WINDOW_TOPLEVEL)gtklib.set_tortoise_icon(self,'hg.ico')accelgroup,mod=gtklib.set_tortoise_keys(self)self.set_title(title)self.set_default_size(320,240)self.connect('delete-event',self.should_live)# acceleratorskey,modifier=gtk.accelerator_parse('Escape')self.add_accelerator('thg-close',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)self.connect('thg-close',self.should_live)self.connect('thg-exit',self.should_live)# log viewerself.log=CmdLogWidget()self.add(self.log)# change window decorationsself.realize()ifself.window:self.window.set_decorations(gtk.gdk.DECOR_BORDER| \
gtk.gdk.DECOR_RESIZEH| \
gtk.gdk.DECOR_TITLE| \
gtk.gdk.DECOR_MENU| \
gtk.gdk.DECOR_MINIMIZE)### public functions ###defget_logwidget(self):""" Return CmdLogWidget instance. """returnself.logdefset_close_hook(self,hook):""" Set hook function. hook: the function called when this dialog is closed. def close_hook(dialog) where 'dialog' is an instance of the CmdLogDialog class. The hook function should return True or False. If True is returned, closing the dialog is prevented. """self.close_hook=hook### signal handlers ###defshould_live(self,*args):ifhasattr(self,'close_hook'):ifself.close_hook(self):self.hide()returnTrue# Structured log types for CmdRunnerLOG_NORMAL=0LOG_ERROR=1classCmdRunner(object):""" Interactive command runner without GUI. By default, there is no GUI (as opposed to CmdDialog). If user interaction is needed (e.g. HTTPS auth), a simple input dialog will be shown. """def__init__(self):self.hgthread=Noneself.dlg=CmdLogDialog()defclose_hook(dialog):self.show_log(False)returnFalseself.dlg.set_close_hook(close_hook)self.clear_buffers()### public functions ###defexecute(self,cmdline,callback,*args,**kargs):""" Execute passed command line using 'hgthread'. When the command terminates, the callback function is invoked with its return code. cmdline: command line string. callback: function invoked after the command terminates. def callback(returncode, useraborted, ...) returncode: See the description of 'hgthread'. useraborted: Whether the command was aborted by the user. return: True if the command was started, False if a command is already running. """ifself.hgthread:returnFalse# clear previous logsself.clear_buffers()# thread startself.hgthread=hgthread.HgThread(cmdline[1:])self.hgthread.start()gobject.timeout_add(10,self.process_queue,callback,args,kargs)returnTruedefis_alive(self):""" Return whether the thread is alive. """returnself.hgthreadandself.hgthread.isAlive()defstop(self):""" Terminate the thread forcibly. """ifself.hgthread:try:self.hgthread.terminate()exceptValueError:pass# race, thread was already terminateddefget_buffer(self):""" Return buffer containing all messages. Note that the buffer will be cleared when the 'execute' method is called, so you need to store this before next execution. """return''.join([chunk[0]forchunkinself.buffer])defget_raw_buffer(self):""" Return structured buffer. Note that the buffer will be cleared when the 'execute' method is called, so you need to store this before next execution. """returnself.bufferdefget_msg_buffer(self):""" Return buffer with regular messages. Note that the buffer will be cleared when the 'execute' method is called, so you need to store this before next execution. """return''.join([chunk[0]forchunkinself.buffer \
ifchunk[1]==LOG_NORMAL])defget_err_buffer(self):""" Return buffer with error messages. Note that the buffer will be cleared when the 'execute' method is called, so you need to store this before next execution. """return''.join([chunk[0]forchunkinself.buffer \
ifchunk[1]==LOG_ERROR])defset_title(self,title):""" Set the title of the command log window. """self.dlg.set_title(title)### internal use functions ###defclear_buffers(self):""" Clear both message and error buffers. """self.buffer=[]self.dlg.log.clear()defshow_log(self,visible=True):ifvisible:ifself.dlg.get_property('visible'):self.dlg.present()else:self.dlg.show_all()else:self.dlg.hide()defprocess_queue(self,callback,args,kargs):# process queueself.hgthread.process_dialogs()# receive messages from queuewhileself.hgthread.getqueue().qsize():try:msg=hglib.toutf(self.hgthread.getqueue().get(0))self.buffer.append((msg,LOG_NORMAL))self.dlg.log.append(msg)exceptQueue.Empty:passwhileself.hgthread.geterrqueue().qsize():try:msg=hglib.toutf(self.hgthread.geterrqueue().get(0))self.buffer.append((msg,LOG_ERROR))self.dlg.log.append(msg,error=True)exceptQueue.Empty:pass# check thread stateifnotself.hgthread.isAlive():returncode=self.hgthread.return_code()self.hgthread=Noneiflen(self.get_err_buffer())>0:self.show_log(True)defcall_callback():callback(returncode,self.get_buffer(),*args,**kargs)gtklib.idle_add_single_call(call_callback)returnFalse# Stop polling this functionelse:returnTrue# Continue polling
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.