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.
# history.py - Changelog dialog for TortoiseHg## Copyright 2007 Brad Schick, brad at gmail . com# Copyright 2008 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.importosimportsysimportgtkimportgobjectimportshutilimporttempfileimportatexitfrommercurialimportui,hg,cmdutil,commands,extensions,util,match,urlfrommercurialimporthbisect,errorfromtortoisehg.util.i18nimport_fromtortoisehg.utilimporthglibfromtortoisehg.hgtk.logview.treeviewimportTreeViewasLogTreeViewfromtortoisehg.hgtk.logview.treeviewimportCOLSasDEFAULT_COLSfromtortoisehg.hgtkimportgdialog,gtklib,hgcmd,gorev,thgstripfromtortoisehg.hgtkimportbackout,status,hgemail,tagadd,update,mergefromtortoisehg.hgtkimportarchive,changeset,thgconfig,thgmq,histdetailsfromtortoisehg.hgtkimportstatusbar,bookmark,thgimportdefcreate_menu(label,callback=None):menuitem=gtk.MenuItem(label,True)ifcallback:menuitem.connect('activate',callback)menuitem.set_border_width(1)returnmenuitemdefcreate_submenu(label,menu):m=create_menu(label)m.set_submenu(menu)returnmclassFilterBox(gtklib.SlimToolbar):'Filter Toolbar for repository log'def__init__(self,tooltips,filter_mode,branch_names):gtklib.SlimToolbar.__init__(self,tooltips)self.filter_mode=filter_modeself.all=gtk.RadioButton(None,_('All'))self.all.set_active(True)self.append_widget(self.all,padding=0)self.tagged=gtk.RadioButton(self.all,_('Tagged'))self.append_widget(self.tagged,padding=0)self.ancestry=gtk.RadioButton(self.all,_('Ancestry'))self.append_widget(self.ancestry,padding=0)self.parents=gtk.RadioButton(self.all,_('Parents'))self.append_widget(self.parents,padding=0)self.heads=gtk.RadioButton(self.all,_('Heads'))self.append_widget(self.heads,padding=0)self.merges=gtk.RadioButton(self.all,_('Merges'))self.append_widget(self.merges,padding=0)self.hidemerges=gtk.CheckButton(_('Hide Merges'))self.append_widget(self.hidemerges,padding=0)self.branches=gtk.RadioButton(self.all)tooltips.set_tip(self.branches,_('Branch Filter'))self.branches.set_sensitive(False)self.append_widget(self.branches,padding=0)self.branchcombo=gtk.combo_box_new_text()self.branchcombo.append_text(_('Branches...'))fornameinbranch_names:self.branchcombo.append_text(hglib.toutf(name))self.branchcombo.set_active(0)self.append_widget(self.branchcombo,padding=0)self.custombutton=gtk.RadioButton(self.all)tooltips.set_tip(self.custombutton,_('Custom Filter'))self.custombutton.set_sensitive(False)self.append_widget(self.custombutton,padding=0)self.filtercombo=gtk.combo_box_new_text()self.filtercombo_entries=(_('Rev Range'),_('File Patterns'),_('Keywords'),_('Date'),_('User'))forfinself.filtercombo_entries:self.filtercombo.append_text(f)if(self.filter_mode>=len(self.filtercombo_entries)orself.filter_mode<0):self.filter_mode=1self.filtercombo.set_active(self.filter_mode)self.append_widget(self.filtercombo,padding=0)searchlist=gtk.ListStore(int,# filtercombo valuestr,# search string (utf-8)str)# mode string (utf-8)entrycombo=gtk.ComboBoxEntry(searchlist,1)cell=gtk.CellRendererText()entrycombo.pack_end(cell,False)entrycombo.add_attribute(cell,'text',2)entry=entrycombo.childself.entrycombo=entrycomboself.entry=entryself.append_widget(entrycombo,expand=True,padding=0)defconnect(self,detailed_signal,handler,*opts):'''Connect an external signal handler to an internal widget Signal format is '[widget_name]_[signal]'.'''widget_name,signal=detailed_signal.split('_')widget=self.__dict__[widget_name]widget.connect(signal,handler,*opts)classGLog(gdialog.GDialog):'GTK+ based dialog for displaying repository logs'definit(self):self.filter='all'self.no_merges=Falseself.lastrevid=Noneself.currevid=Noneself.origtip=len(self.repo)self.ready=Falseself.filterbox=Noneself.details_model=Noneself.syncbox=Noneself.filteropts=Noneself.bundledir=Noneself.bfile=Noneself.npreviews=0self.outgoing=[]self.useproxy=Noneself.revrange=Noneself.forcepush=Falseself.bundle_autoreject=Falseself.runner=hgcmd.CmdRunner()os.chdir(self.repo.root)self.exs=[nameforname,moduleinextensions.extensions()]defget_help_url(self):return'changelog.html'defdelete(self,window,event):ifnotself.should_live():self.destroy()else:returnTruedefshould_live(self,widget=None,event=None):live=Falseifself.bfileandnotself.bundle_autoreject:t=_('New changesets from the preview bundle are still pending.''\n\nAccept or reject the new changesets?')# response: 0=Yes, 1=No, 2=Cancelresponse=gdialog.CustomPrompt(_('Accept new Changesets'),t,self,(_('&Accept'),_('&Reject'),_('&Cancel')),2,2).run()ifresponse==0:self.apply_clicked(None)elifresponse==2:live=Trueifnotlive:self._destroying(widget)returnlivedefget_title(self):str=self.get_reponame()+' - '+_('Repository Explorer')ifself.bfile:str+=_(' (Bundle Preview)')returnstrdefget_icon(self):return'menulog.ico'defget_default_setting(self):return'tortoisehg.authorcolor'defparse_opts(self):# Disable quiet to get full log infoself.ui.quiet=Falsedefget_tbbuttons(self):tbar=[self.make_toolbutton(gtk.STOCK_REFRESH,_('Re_fresh'),self.refresh_clicked,name='refresh',tip=_('Reload revision history')),]if'mq'inself.exs:self.mqtb=self.make_toolbutton(gtk.STOCK_DIRECTORY,_('Patch Queue'),self.mq_clicked,name='mq',tip=_('Show/Hide Patch Queue'), toggle=True,
icon='menupatch.ico')
tbar += [gtk.SeparatorToolItem(), self.mqtb]
+ sep = gtk.SeparatorToolItem()+ sep.set_expand(True)+ sep.set_draw(False)+ tbar.append(sep)+ tbar += [+ self.make_toolbutton(gtk.STOCK_OK, _('Commit'),+ self.launch, userdata='commit', icon='menucommit.ico',+ tip=_('Launch commit tool')),+ self.make_toolbutton(gtk.STOCK_OK, _('Datamine'),+ self.launch, userdata='datamine', icon='menurepobrowse.ico',+ tip=_('Launch data mining tool')),+ self.make_toolbutton(gtk.STOCK_OK, _('Recovery'),+ self.launch, userdata='recovery', icon='general.ico',+ tip=_('Launch recovery tool')),+ self.make_toolbutton(gtk.STOCK_OK, _('Serve'),+ self.launch, userdata='serve', icon='proxy.ico',+ tip=_('Launch web server')),+ self.make_toolbutton(gtk.STOCK_OK, _('Shelve'),+ self.launch, userdata='shelve', icon='shelve.ico',+ tip=_('Launch shelve tool')),+ ] return tbar
def get_menu_list(self):
deftoggle_proxy(menuitem):self.useproxy=menuitem.get_active()deftoggle_force(menuitem):self.forcepush=menuitem.get_active()defrefresh(menuitem,resetmarks):ifresetmarks:self.stbar.set_idle_text(None)self.outgoing=[]self.origtip=len(self.repo)self.reload_log()defnavigate(menuitem,revname):ifrevname:self.goto_rev(revname)elifself.gorev_dialog:self.gorev_dialog.show()self.gorev_dialog.present()else:self.show_goto_dialog()defdisable_maxdiff(menuitem):ifmenuitem.get_active():hglib._maxdiff=sys.maxintelse:hglib._maxdiff=Noneself.reload_log()lb=self.get_live_branches()bmenus=[]iflen(lb)>1or(lbandlb[0]!='default'):bmenus.append(dict(text='----'))fornameinlb[:10]:bmenus.append(dict(text=hglib.toutf(name),func=navigate,args=[name]))fnc=self.toggle_view_columnifself.repo.ui.configbool('tortoisehg','disable-syncbar'):sync_bar_item=[]else:sync_bar_item=[dict(text=_('Sync Bar'),ascheck=True,func=self.toggle_show_syncbar,check=self.show_syncbar)]if'mq'inself.exs:mq_item=[dict(text=_('Patch Queue'),name='mq',ascheck=True,func=self.mq_clicked,check=self.setting_mqvis)]else:mq_item=[]return[(_('_View'),[dict(text=_('Load more Revisions'),name='load-more',func=self.more_clicked,icon=gtk.STOCK_GO_DOWN),dict(text=_('Load all Revisions'),name='load-all',func=self.load_all_clicked,icon=gtk.STOCK_GOTO_BOTTOM),dict(text='----'),dict(text=_('Toolbar'),ascheck=True,check=self.show_toolbar,func=self.toggle_show_toolbar),]+sync_bar_item+[dict(text=_('Filter Bar'),ascheck=True,func=self.toggle_show_filterbar,check=self.show_filterbar),]+mq_item+[dict(text='----'),dict(text=_('Refresh'),func=refresh,args=[False],icon=gtk.STOCK_REFRESH),dict(text=_('Reset Marks'),func=refresh,args=[True],icon=gtk.STOCK_CLEAR),dict(text='----'),dict(text=_('Choose Details...'),func=self.details_clicked,icon='general.ico'),dict(text='----'),dict(name='compact-graph',text=(_('Compact Graph')),ascheck=True,func=self.toggle_compactgraph,check=self.compactgraph),dict(name='color-by-branch',text=_('Color by Branch'),ascheck=True,func=self.toggle_branchcolor,check=self.branch_color),dict(text=_('Ignore Max Diff Size'),ascheck=True,func=disable_maxdiff),]),(_('_Navigate'),[dict(text=_('Tip'),func=navigate,args=['tip'],icon=gtk.STOCK_ABOUT),dict(text=_('Working Parent'),func=navigate,args=['.'],icon=gtk.STOCK_HOME),dict(text='----'),dict(text=_('Revision...'),func=navigate,args=[None],icon=gtk.STOCK_JUMP_TO),]+bmenus),(_('_Synchronize'),[dict(text=_('Incoming'),name='incoming',func=self.incoming_clicked,icon=gtk.STOCK_GO_DOWN),dict(text=_('Pull'),name='pull',func=self.pull_clicked,icon=gtk.STOCK_GOTO_BOTTOM),dict(text=_('Outgoing'),name='outgoing',func=self.outgoing_clicked,icon=gtk.STOCK_GO_UP),dict(text=_('Push'),name='push',func=self.push_clicked,icon=gtk.STOCK_GOTO_TOP),dict(text=_('Email...'),name='email',func=self.email_clicked,icon=gtk.STOCK_GOTO_LAST),dict(text=_('Stop'),name='stop',sensitive=False,func=self.stop_clicked,icon=gtk.STOCK_STOP),dict(text='----'),dict(text=_('Accept Bundle'),name='accept',sensitive=bool(self.bfile),func=self.apply_clicked,icon=gtk.STOCK_APPLY),dict(text=_('Reject Bundle'),name='reject',sensitive=bool(self.bfile),func=self.reject_clicked,icon=gtk.STOCK_DIALOG_ERROR),dict(text='----'),dict(text=_('Import...'),name='import',func=self.import_clicked,icon='menuimport.ico'),dict(text=_('Add Bundle...'),name='add-bundle',sensitive=notbool(self.bfile),func=self.add_bundle_clicked,icon=gtk.STOCK_ADD),dict(text='----'),dict(text=_('Use proxy server'),name='use-proxy-server',ascheck=True,func=toggle_proxy),dict(text=_('Force push'),ascheck=True,func=toggle_force),])]deftoggle_view_column(self,button,property):active=button.get_active()self.graphview.set_property(property,active)deftoggle_branchcolor(self,button):active=button.get_active()ifself.branch_color!=active:self.graphview.set_property('branch-color',active)self.branch_color=activeself.reload_log()deftoggle_compactgraph(self,button):active=button.get_active()ifself.compactgraph!=active:self.compactgraph=activeself.reload_log()deftoggle_show_filterbar(self,button):self.show_filterbar=button.get_active()ifself.filterboxisnotNone:self.filterbox.set_property('visible',self.show_filterbar)deftoggle_show_syncbar(self,button):self.show_syncbar=button.get_active()ifself.syncboxisnotNone:self.syncbox.set_property('visible',self.show_syncbar)deftoggle_show_toolbar(self,button):self.show_toolbar=button.get_active()self.syncbox.set_visible('reload',notself.show_toolbar)self._show_toolbar(self.show_toolbar)defmore_clicked(self,button,data=None):self.graphview.next_revision_batch(self.limit)defload_all_clicked(self,button,data=None):self.graphview.load_all_revisions()self.cmd_set_sensitive('load-more',False)self.cmd_set_sensitive('load-all',False)defselection_changed(self,graphview):'Graphview reports a new row selected'treeview=graphview.treeviewpath,col=treeview.get_cursor()ifnotpath:self.currevid=NonereturnFalseself.currevid=graphview.get_revid_at_path(path)self.ancestrybutton.set_sensitive(True)ifself.currevid!=self.lastrevid:self.lastrevid=self.currevidself.changeview.opts['rev']=[str(self.currevid)]self.changeview.load_details(self.currevid)returnFalsedefrevisions_loaded(self,graphview):'Graphview reports log generator has exited'ifnotgraphview.graphdata:self.changeview.clear()self.cmd_set_sensitive('load-more',False)self.cmd_set_sensitive('load-all',False)defdetails_clicked(self,toolbutton,data=None):self.show_details_dialog()defshow_details_dialog(self):columns={}columns['graph']=(self.graphcol,_('Graph'),'graphcol','graph')defcolumn(col,text):prop=col+'-column-visible'vis=self.graphview.get_property(prop)columns[col]=(vis,text,prop,col)column('rev',_('Revision Number'))column('id',_('Changeset ID'))column('revhex',_('Revision Number/ID'))column('branch',_('Branch Name'))column('changes',_('Changes'))column('msg',_('Summary'))column('user',_('User'))column('date',_('Local Date'))column('utc',_('UTC Date'))column('age',_('Age'))column('tag',_('Tags'))model=gtk.ListStore(gobject.TYPE_BOOLEAN,gobject.TYPE_STRING,gobject.TYPE_STRING,gobject.TYPE_STRING)forcinself.graphview.get_columns():vis,text,prop,col=columns[c]model.append([vis,text,prop,col])self.details_model=modeldlg=histdetails.LogDetailsDialog(model,self.apply_details)dlg.show()defapply_details(self):ifself.details_model:columns=[]forshow,uitext,property,colnameinself.details_model:columns.append(colname)self.graphview.set_columns(columns)self.column_order=' '.join(columns)reload=Falseforshow,uitext,property,colnameinself.details_model:ifproperty=='graphcol':ifself.graphcol!=show:self.graphcol=showreload=Trueself.cmd_set_sensitive('compact-graph',self.graphcol)self.cmd_set_sensitive('color-by-branch',self.graphcol)else:self.graphview.set_property(property,show)self.showcol[property]=showifreload:self.reload_log()deffilter_entry_changed(self,entrycombo,filtercombo):row=entrycombo.get_active()ifrow<0:returnmode,text,display=entrycombo.get_model()[row]filtercombo.set_active(mode)entrycombo.child.set_text(text)self.activate_filter(text,mode)deffilter_entry_activated(self,entry,combo):'User pressed enter in the filter entry'mode=combo.get_active()text=entry.get_text()ifnottext:returnrow=[mode,text,combo.get_active_text()]model=self.entrycombo.get_model()forrinmodel:ifr[0]==row[0]andr[1]==row[1]:breakelse:self.entrycombo.get_model().append(row)self.activate_filter(text,mode)defactivate_filter(self,text,mode):opts={}ifmode==0:# Rev Rangetry:opts['revlist']=cmdutil.revrange(self.repo,[text])exceptException,e:gdialog.Prompt(_('Invalid revision range'),str(e),self).run()returnelifmode==1:# File Patternsopts['pats']=[w.strip()forwintext.split(',')]elifmode==2:# Keywordsopts['keyword']=[w.strip()forwintext.split(',')]elifmode==3:# Datetry:# return of matchdate not used, just sanity checkingutil.matchdate(text)opts['date']=textexcept(ValueError,util.Abort),e:gdialog.Prompt(_('Invalid date specification'),str(e),self).run()returnelifmode==4:# Useropts['user']=[w.strip()forwintext.split(',')]else:returnself.custombutton.set_active(True)self.filter='custom'self.reload_log(**opts)deffilter_selected(self,widget,type):iftype=='no_merges':self.no_merges=widget.get_active()self.reload_log()returnifnotwidget.get_active():returniftype=='branch':self.lastbranchrow=Noneself.select_branch(self.branchcombo)returnself.filter=typeself.filteropts=Noneself.reload_log()defupdate_hide_merges_button(self):compatible=self.filterin['all','branch','custom']ifcompatible:self.hidemerges.set_sensitive(True)else:self.hidemerges.set_active(False)self.hidemerges.set_sensitive(False)self.no_merges=Falsedefpatch_selected(self,mqwidget,revid,patchname):ifrevid<0:patchfile=os.path.join(self.repo.root,'.hg','patches',patchname)self.currevid=self.lastrevid=Noneself.changeview.load_patch_details(patchfile)else:self.currevid=revidifself.currevid!=self.lastrevid:self.lastrevid=self.currevidself.changeview.opts['rev']=[str(self.currevid)]self.changeview.load_details(self.currevid)defrepo_invalidated(self,mqwidget):self.reload_log()defprepare_display(self):'Called at end of display() method'self.ready=Trueroot=self.repo.rootos.chdir(root)# for paths relative to repo rootself.origtip=len(self.repo)self.graphview.set_property('branch-color',self.branch_color)# ignore file patterns that imply repo rootiflen(self.pats)==1andself.pats[0]in(root,root+os.sep,''):self.pats=[]opts=self.optsifopts['filehist']:self.custombutton.set_active(True)self.filter='custom'self.filtercombo.set_active(1)self.filterentry.set_text(opts['filehist'])opts['pats']=[opts['filehist']]elifself.pats:self.custombutton.set_active(True)self.filter='custom'self.filtercombo.set_active(1)self.filterentry.set_text(', '.join(self.pats))opts['pats']=self.patsif'bundle'inopts:self.set_bundlefile(opts['bundle'])self.bundle_autoreject=Trueelse:self.reload_log(**opts)self.filterbox.set_property('visible',self.show_filterbar)self.filterbox.set_no_show_all(True)self.syncbox.set_property('visible',self.show_syncbar)self.syncbox.set_no_show_all(True)forcolin[colforcolinDEFAULT_COLS.split()ifcol!='graph']:ifcolinself.showcol:self.graphview.set_property(col+'-column-visible',self.showcol[col])try:self.graphview.set_columns(self.column_order.split())exceptKeyError:# ignore unknown column names, these could originate from garbeled# persisted datapassself.cmd_set_sensitive('compact-graph',self.graphcol)self.cmd_set_sensitive('color-by-branch',self.graphcol)item=self.get_menuitem('use-proxy-server')ifui.ui().config('http_proxy','host'):item.set_sensitive(True)item.set_active(True)else:item.set_sensitive(False)# enable MQ panelself.enable_mqpanel()defget_proxy_args(self):item=self.get_menuitem('use-proxy-server')ifitem.get_property('sensitive')andnotitem.get_active():return['--config','http_proxy.host=']else:return[]defget_graphlimit(self,suggestion):limit_opt=self.repo.ui.config('tortoisehg','graphlimit','500')l=0forlimitin(suggestion,limit_opt):try:l=int(limit)ifl>0:returnlexcept(TypeError,ValueError):passreturnlor500defsave_settings(self):settings=gdialog.GDialog.save_settings(self)settings['glog-vpane']=self.vpaned.get_position()settings['glog-hpane']=self.hpaned.get_position()ifhasattr(self,'mqpaned')andself.mqwidget.has_patch():curpos=self.mqpaned.get_position()settings['glog-mqpane']=curposorself.setting_mqhpossettings['glog-mqvis']=bool(curpos)else:settings['glog-mqpane']=self.setting_mqhpossettings['glog-mqvis']=self.setting_mqvissettings['branch-color']=self.graphview.get_property('branch-color')settings['show-toolbar']=self.show_toolbarsettings['show-filterbar']=self.show_filterbarsettings['show-syncbar']=self.show_syncbarsettings['graphcol']=self.graphcolsettings['compactgraph']=self.compactgraphforcolin[colforcolinDEFAULT_COLS.split()ifcol!='graph']:vis=self.graphview.get_property(col+'-column-visible')settings['glog-vis-'+col]=vissettings['filter-mode']=self.filtercombo.get_active()settings['column-order']=' '.join(self.graphview.get_columns())returnsettingsdefload_settings(self,settings):'Called at beginning of display() method'gdialog.GDialog.load_settings(self,settings)self.setting_vpos=settings.get('glog-vpane',-1)self.setting_hpos=settings.get('glog-hpane',-1)self.setting_mqhpos=settings.get('glog-mqpane',140)or140self.setting_mqvis=settings.get('glog-mqvis',False)self.branch_color=settings.get('branch-color',False)self.show_toolbar=settings.get('show-toolbar',True)self.show_filterbar=settings.get('show-filterbar',True)self.show_syncbar=settings.get('show-syncbar',True)ifself.repo.ui.configbool('tortoisehg','disable-syncbar'):self.show_syncbar=Falseself.graphcol=settings.get('graphcol',True)self.compactgraph=settings.get('compactgraph',False)self.showcol={}forcolin[colforcolinDEFAULT_COLS.split()ifcol!='graph']:key='glog-vis-'+colifkeyinsettings:self.showcol[col]=settings[key]self.filter_mode=settings.get('filter-mode',1)order=settings.get('column-order',DEFAULT_COLS)order_list,def_list=order.split(),DEFAULT_COLS.split()order_len,def_len=len(order_list),len(def_list)iforder_len!=def_len:# add newly added columns if existsadded=set(def_list).difference(set(order_list))ifadded:order_list+=list(added)# remove obsoleted columns if existsorder_list=[cforcinorder_listifcindef_list]order=' '.join(order_list)self.column_order=orderdefshow_toolbar_on_start(self):returnself.show_toolbardefrefresh_model(self):'Refresh data in the history model, without reloading graph'ifself.graphview.model:self.graphview.model.refresh()# refresh MQ widget if existsifhasattr(self,'mqwidget'):self.mqwidget.refresh()# force a redraw of the visible rowsself.graphview.hide()self.graphview.show()defreload_log(self,**kwopts):'Send refresh event to treeview object'self.update_hide_merges_button()self.changeview.clear_cache()opts={'date':None,'no_merges':False,'only_merges':False,'keyword':[],'branch':None,'pats':[],'filehist':None,'revrange':[],'revlist':[],'noheads':False,'branch-view':False,'rev':[],'user':[]}ifself.filteroptsandnotkwopts:opts=self.filteroptsopts.update(kwopts)# handle strips, rebases, etcself.origtip=min(len(self.repo),self.origtip)ifnotself.bfile:self.npreviews=0opts['branch-view']=self.compactgraphopts['outgoing']=self.outgoingopts['orig-tip']=self.origtipopts['npreviews']=self.npreviewsopts['no_merges']=self.no_mergesself.cmd_set_sensitive('load-more',len(self.repo)>0)self.cmd_set_sensitive('load-all',len(self.repo)>0)self.ancestrybutton.set_sensitive(False)pats=opts.get('pats',[])self.changeview.pats=patsself.lastrevid=Nonedefftitle(filtername):t=self.get_title()iffilternameisnotNone:t=t+' - '+filternameself.set_title(t)ifself.filter!='custom':self.filterentry.set_text('')graphcol=self.graphcolifself.no_merges:graphcol=Falsefilterprefix=_('Filter')filtertext=filterprefix+': 'ifself.filter=='branch':branch=opts.get('branch',None)self.graphview.refresh(graphcol,None,opts)ftitle(_('%s branch')%branch)filtertext+=_("Branch '%s'")%branchelifself.filter=='custom':npats=hglib.normpats(pats)iflen(npats)==1:kind,name=match._patsplit(npats[0],None)ifkind=='path'andnotos.path.isdir(name):ftitle(_('file history: ')+hglib.toutf(name))opts['filehist']=nameself.graphview.refresh(graphcol,[name],opts)ifnotopts.get('filehist'):ftitle(_('custom filter'))self.graphview.refresh(False,npats,opts)filtertext+=self.filtercombo.get_active_text()elifself.filter=='all':ftitle(None)self.graphview.refresh(graphcol,None,opts)filtertext=''elifself.filter=='only_merges':ftitle(_('merges'))opts['only_merges']=Trueself.graphview.refresh(False,[],opts)filtertext+=_('only Merges')elifself.filter=='ancestry':ftitle(_('revision ancestry'))range=[self.currevid,0]opts['noheads']=Trueopts['revrange']=rangeself.graphview.refresh(graphcol,None,opts)filtertext+=_("Ancestry of %s")%self.currevidelifself.filter=='tagged':ftitle(_('tagged revisions'))tagged=[]fort,rinself.repo.tagslist():hr=self.repo[r].rev()ifhrnotintagged:tagged.insert(0,hr)opts['revlist']=taggedself.graphview.refresh(False,[],opts)filtertext+=_("Tagged Revisions")elifself.filter=='parents':ftitle(_('working parents'))repo_parents=[x.rev()forxinself.repo.parents()]opts['revlist']=[str(x)forxinrepo_parents]self.graphview.refresh(False,[],opts)filtertext+=_("Parents")elifself.filter=='heads':ftitle(_('heads'))heads=[self.repo[x].rev()forxinself.repo.heads()]opts['revlist']=[str(x)forxinheads]self.graphview.refresh(False,[],opts)filtertext+=_("Heads")nomergestext=_('no Merges')ifself.no_merges:iffiltertext:filtertext+=', %s'%nomergestextelse:filtertext='%s: %s'%(filterprefix,nomergestext)self.stbar.set_right2_text(filtertext)# refresh MQ widget if existsifhasattr(self,'mqwidget'):self.mqwidget.refresh()# Remember options to next time reload_log is calledself.filteropts=optsdeftree_context_menu(self):m=gtklib.MenuItems()m.append(create_menu(_('Visualize Change'),self.vdiff_change))m.append(create_menu(_('Di_splay Change'),self.show_status))m.append(create_menu(_('Diff to local'),self.vdiff_local))m.append_sep()m.append(create_menu(_('_Copy hash'),self.copy_hash))ifself.bfile:ifself.currevid>=len(self.repo)-self.npreviews:m.append_sep()m.append(create_menu(_('Pull to here'),self.pull_to))menu=m.create_menu()menu.show_all()returnmenuifself.repo[self.currevid].node()inself.outgoing:m.append_sep()m.append(create_menu(_('Push to here'),self.push_to))m.append_sep()m.append(create_menu(_('_Update...'),self.checkout))cmenu_merge=create_menu(_('_Merge with...'),self.domerge)m.append(cmenu_merge)cmenu_backout=create_menu(_('Backout...'),self.backout_rev)m.append(cmenu_backout)m.append(create_menu(_('_Revert'),self.revert))m.append_sep()m.append(create_submenu(_('Export'),self.export_context_menu()))m.append_sep()m.append(create_submenu(_('Tag'),self.tags_context_menu()))m.append_sep()# disable/enable menus as requiredparents=self.repo.parents()iflen(parents)>1:can_merge=Falsecan_backout=Falseelse:pctx=parents[0]cctx=self.repo[self.currevid]actx=cctx.ancestor(pctx)can_merge=actx!=pctxorpctx.branch()!=cctx.branch()can_backout=actx==cctxcmenu_merge.set_sensitive(can_merge)cmenu_backout.set_sensitive(can_backout)# need mq extension for strip commandif'mq'inself.exs:m.append(create_submenu(_('Mercurial Queues'),self.mq_context_menu()))# need transplant extension for transplant commandif'transplant'inself.exs:m.append(create_menu(_('Transp_lant to local'),self.transplant_rev))m.append_sep()m.append(create_submenu(_('Bisect'),self.bisect_context_menu()))menu=m.create_menu()menu.show_all()returnmenudefexport_context_menu(self):m=gtklib.MenuItems()m.append(create_menu(_('_Export Patch...'),self.export_patch))m.append(create_menu(_('E_mail Patch...'),self.email_patch))m.append(create_menu(_('_Bundle rev:tip...'),self.bundle_rev_to_tip))m.append(create_menu(_('_Archive...'),self.archive))returnm.create_menu()deftags_context_menu(self):m=gtklib.MenuItems()m.append(create_menu(_('Add/Remove _Tag...'),self.add_tag))if'bookmarks'inself.exs:m.append(create_menu(_('Add/Move/Remove B_ookmark...'),self.add_bookmark))m.append(create_menu(_('Rename Bookmark...'),self.rename_bookmark))ifself.repo.ui.configbool('bookmarks','track.current'):m.append(create_menu(_('Set Current Bookmark...'),self.current_bookmark))returnm.create_menu()defmq_context_menu(self):m=gtklib.MenuItems()cmenu_qimport=create_menu(_('QImport Revision'),self.qimport_rev)cmenu_strip=create_menu(_('Strip Revision...'),self.strip_rev)try:ctx=self.repo[self.currevid]qbase=self.repo['qbase']actx=ctx.ancestor(qbase)ifself.repo['qparent']==ctx:cmenu_qimport.set_sensitive(True)cmenu_strip.set_sensitive(False)elifactx==qbaseoractx==ctx:# we're in the mq revision range or the mq# is a descendant of uscmenu_qimport.set_sensitive(False)cmenu_strip.set_sensitive(False)except:passm.append_sep()m.append(cmenu_qimport)m.append(cmenu_strip)returnm.create_menu()defbisect_context_menu(self):m=gtklib.MenuItems()m.append(create_menu(_('Reset'),self.bisect_reset))m.append(create_menu(_('Mark as good'),self.bisect_good))m.append(create_menu(_('Mark as bad'),self.bisect_bad))m.append(create_menu(_('Skip testing'),self.bisect_skip))returnm.create_menu()defrestore_single_sel(self,widget,*args):self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)ifself.origsel:self.tree.get_selection().select_path(self.origsel)self.revrange=Nonedeftree_diff_context_menu(self):m=gtklib.MenuItems()m.append(create_menu(_('_Diff with selected'),self.diff_revs))m.append(create_menu(_('Visual Diff with selected'),self.vdiff_selected))ifself.bfile:menu=m.create_menu()menu.connect_after('selection-done',self.restore_single_sel)menu.show_all()returnmenum.append_sep()m.append(create_menu(_('Email from here to selected...'),self.email_revs))m.append(create_menu(_('Bundle from here to selected...'),self.bundle_revs))m.append(create_menu(_('Export Patches from here to selected...'),self.export_revs))m.append_sep()cmenu_merge=create_menu(_('_Merge with...'),self.domerge)m.append(cmenu_merge)m.append_sep()# disable/enable menus as requiredparents=self.repo.parents()iflen(parents)>1:can_merge=Falseelse:rev0,rev1=self.revrangec0,c1=self.repo[rev0],self.repo[rev1]can_merge=c0.branch()!=c1.branch()orc0.ancestor(c1)!=c1cmenu_merge.set_sensitive(can_merge)# need transplant extension for transplant commandif'transplant'inself.exs:m.append(create_menu(_('Transplant Revision range to local'),self.transplant_revs))# need rebase extension for rebase commandif'rebase'inself.exs:m.append(create_menu(_('Rebase on top of selected'),self.rebase_selected))# need MQ extension for qimport commandif'mq'inself.exs:m.append(create_menu(_('qimport from here to selected'),self.qimport_revs))m.append_sep()m.append(create_menu(_('Select common ancestor revision'),self.select_common_ancestor))menu=m.create_menu()menu.connect_after('selection-done',self.restore_single_sel)menu.show_all()returnmenudefget_body(self):self.connect('delete-event',self.delete)self.gorev_dialog=Noneself.stbar=statusbar.StatusBar()self.limit=self.get_graphlimit(None)# Allocate TreeView instance to use internallylimit=self.limitifself.opts['limit']:limit=self.get_graphlimit(self.opts['limit'])self.graphview=LogTreeView(self.repo,limit,self.stbar)# Allocate ChangeSet instance to use internallyself.changeview=changeset.ChangeSet(self.ui,self.repo,self.cwd,[],self.opts,self.stbar)self.changeview.display(False)self.changeview.glog_parent=self# Add extra toolbar buttonssep=gtk.SeparatorToolItem()sep.set_expand(True)sep.set_draw(False)loadnext=self.make_toolbutton(gtk.STOCK_GO_DOWN,_('Load more'),self.more_clicked,tip=_('load more revisions'),name='load-more')loadall=self.make_toolbutton(gtk.STOCK_GOTO_BOTTOM,_('Load all'),self.load_all_clicked,tip=_('load all revisions'),name='load-all')tbar=self.changeview.get_tbbuttons()tbar+=[sep,loadnext,loadall]fortbuttonintbar:self.toolbar.insert(tbutton,-1)# PyGtk 2.6 and below did not automatically register typesifgobject.pygtk_version<(2,8,0):gobject.type_register(LogTreeView)self.tree=self.graphview.treeviewself.graphview.connect('revision-selected',self.selection_changed)self.graphview.connect('revisions-loaded',self.revisions_loaded)self.tree.connect('popup-menu',self.tree_popup_menu)self.tree.connect('button-press-event',self.tree_button_press)self.tree.connect('row-activated',self.tree_row_act)accelgroup=gtk.AccelGroup()self.add_accel_group(accelgroup)mod=gtklib.get_thg_modifier()key,modifier=gtk.accelerator_parse(mod+'d')self.tree.add_accelerator('thg-diff',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)self.tree.connect('thg-diff',self.thgdiff)key,modifier=gtk.accelerator_parse(mod+'p')self.tree.add_accelerator('thg-parent',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)self.tree.connect('thg-parent',self.thgparent)key,modifier=gtk.accelerator_parse(mod+'g')self.tree.add_accelerator('thg-revision',accelgroup,key,modifier,gtk.ACCEL_VISIBLE)self.tree.connect('thg-revision',self.thgnavigate)self.connect('thg-refresh',self.thgrefresh)# synch barself.syncbox=gtklib.SlimToolbar(self.tooltips)syncbox=self.syncboxrefresh=syncbox.append_button(gtk.STOCK_REFRESH,_('Reload revision history'),group='reload')syncbox.append_separator(group='reload')incoming=syncbox.append_button(gtk.STOCK_GO_DOWN,_('Download and view incoming changesets'))apply=syncbox.append_button(gtk.STOCK_APPLY,_('Accept changes from Bundle preview'),group='bundle')reject=syncbox.append_button(gtk.STOCK_DIALOG_ERROR,_('Reject changes from Bundle preview'),group='bundle')pull=syncbox.append_button(gtk.STOCK_GOTO_BOTTOM,_('Pull incoming changesets'))importbtn=syncbox.append_button('menuimport.ico',_('Import patches'))syncbox.append_separator()outgoing=syncbox.append_button(gtk.STOCK_GO_UP,_('Determine and mark outgoing changesets'))push=syncbox.append_button(gtk.STOCK_GOTO_TOP,_('Push outgoing changesets'))email=syncbox.append_button(gtk.STOCK_GOTO_LAST,_('Email outgoing changesets'))syncbox.append_separator(group='stop')stop=syncbox.append_button(gtk.STOCK_STOP,_('Stop current transaction'),group='stop')syncbox.set_visible('reload',notself.show_toolbar)syncbox.set_enable('bundle',False)syncbox.set_enable('stop',False)self.syncbar_apply=applyself.syncbar_reject=rejectself.stop_button=stop## target path comboboxurllist=gtk.ListStore(str,# path (utf-8)str)# alias (utf-8)urlcombo=gtk.ComboBoxEntry(urllist,0)cell=gtk.CellRendererText()urlcombo.pack_end(cell,False)urlcombo.add_attribute(cell,'text',1)self.urlcombo=urlcomboself.pathentry=urlcombo.get_child()syncbox.append_widget(urlcombo,expand=True)self.update_urllist()## post pull drop-down listppullbox=gtk.HBox()syncbox.append_widget(ppullbox)ppullbox.pack_start(gtk.Label(_('After Pull:')),False,False,4)ppulldata=[('none',_('Nothing')),('update',_('Update'))]ppull=self.repo.ui.config('tortoisehg','postpull','none')if'fetch'inself.exsor'fetch'==ppull:ppulldata.append(('fetch',_('Fetch')))if'rebase'inself.exsor'rebase'==ppull:ppulldata.append(('rebase',_('Rebase')))ppulllist=gtk.ListStore(str,# namestr)# label (utf-8)ppullcombo=gtk.ComboBox(ppulllist)ppullbox.pack_start(ppullcombo,False,False)cell=gtk.CellRendererText()ppullcombo.pack_start(cell)ppullcombo.add_attribute(cell,'text',1)forname,labelinppulldata:ppulllist.append((name,label))self.ppullcombo=ppullcomboself.ppulldata=ppulldataself.ppullbox=ppullboxself.update_postpull(ppull)## add conf buttonconf=syncbox.append_button(gtk.STOCK_PREFERENCES,_('Configure aliases and after pull behavior'))## connect syncbar buttonsrefresh.connect('clicked',self.refresh_clicked)incoming.connect('clicked',self.incoming_clicked)pull.connect('clicked',self.pull_clicked)importbtn.connect('clicked',self.import_clicked)outgoing.connect('clicked',self.outgoing_clicked)push.connect('clicked',self.push_clicked)apply.connect('clicked',self.apply_clicked)reject.connect('clicked',self.reject_clicked)conf.connect('clicked',self.conf_clicked,urlcombo)email.connect('clicked',self.email_clicked)stop.connect('clicked',self.stop_clicked)# filter barself.filterbox=FilterBox(self.tooltips,self.filter_mode,self.get_live_branches())filterbox=self.filterboxself.ancestrybutton=filterbox.ancestryself.hidemerges=filterbox.hidemergesself.branchbutton=filterbox.branchesself.lastbranchrow=Noneself.branchcombo=filterbox.branchcomboself.custombutton=filterbox.custombuttonself.filter_mode=filterbox.filter_modeself.filtercombo=filterbox.filtercomboself.filterentry=filterbox.entryself.entrycombo=filterbox.entrycombofcon=self.filterbox.connectfsel=self.filter_selectedfcon('all_toggled',fsel,'all')fcon('tagged_toggled',fsel,'tagged')fcon('ancestry_toggled',fsel,'ancestry')fcon('parents_toggled',fsel,'parents')fcon('heads_toggled',fsel,'heads')fcon('merges_toggled',fsel,'only_merges')fcon('hidemerges_toggled',fsel,'no_merges')fcon('branches_toggled',fsel,'branch')fcon('branchcombo_changed',self.select_branch)fcon('entry_activate',self.filter_entry_activated,self.filtercombo)fcon('entrycombo_changed',self.filter_entry_changed,self.filtercombo)midpane=gtk.VBox()midpane.pack_start(syncbox,False)midpane.pack_start(filterbox,False)midpane.pack_start(self.graphview)midpane.show_all()# MQ widgetif'mq'inself.exs:# create MQWidgetself.mqwidget=thgmq.MQWidget(self.repo,self.stbar,accelgroup,self.tooltips)self.mqwidget.connect('patch-selected',self.patch_selected)self.mqwidget.connect('repo-invalidated',self.repo_invalidated)defwrapframe(widget):frame=gtk.Frame()frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)frame.add(widget)returnframeself.mqpaned=gtk.HPaned()self.mqpaned.add1(wrapframe(self.mqwidget))self.mqpaned.add2(wrapframe(midpane))# register signal handlerdefnotify(paned,gparam):ifnothasattr(self,'mqtb'):returnpos=paned.get_position()ifself.cmd_get_active('mq'):ifpos<140:paned.set_position(140)else:ifpos!=0:paned.set_position(0)self.mqpaned.connect('notify::position',notify)midpane=self.mqpaned# Add ChangeSet instance to bottom half of vpaneself.changeview.graphview=self.graphviewself.hpaned=self.changeview.get_body()self.vpaned=gtk.VPaned()self.vpaned.pack1(midpane,True,False)self.vpaned.pack2(self.hpaned)gtklib.idle_add_single_call(self.realize_settings)vbox=gtk.VBox()vbox.pack_start(self.vpaned,True,True)returnvboxdefget_extras(self):returnself.stbardefrefresh_on_marker_change(self,oldlen,oldmarkers,newmarkers):# Note that oldmarkers/newmarkers may be either dicts# (for add/remove bookmarks, which can also 'move'# bookmarks), or lists (everything else)self.repo.invalidate()self.changeview.clear_cache()iflen(self.repo)!=oldlen:self.reload_log()else:ifnewmarkers!=oldmarkers:self.refresh_model()defrefresh_on_current_marker_change(self,oldlen,oldmarkers,oldcurrent,newmarkers,newcurrent):self.repo.invalidate()self.changeview.clear_cache()iflen(self.repo)!=oldlen:self.reload_log()else:ifnewmarkers!=oldmarkersor \
oldcurrent!=newcurrent:self.refresh_model()defapply_clicked(self,button):combo=self.ppullcombolist,iter=combo.get_model(),combo.get_active_iter()ppull,label=list[list.get_path(iter)]ifppull=='fetch':cmd=['fetch','--message','merge']# load the fetch extension explicitlyhglib.loadextension(self.ui,'fetch')else:cmd=['pull']ifppull=='update':cmd.append('--update')elifppull=='rebase':cmd.append('--rebase')# load the rebase extension explicitlyhglib.loadextension(self.ui,'rebase')cmdline=['hg']+cmd+[self.bfile]defcallback(return_code,*args):self.stbar.end()self.remove_overlay('--rebase'incmd)ifself.runner.execute(cmdline,callback):self.runner.set_title(_('Applying bundle'))self.stbar.begin(_('Applying bundle...'))else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defremove_overlay(self,resettip):self.bfile=Noneself.npreviews=0ifisinstance(self.origurl,int):self.urlcombo.set_active(self.origurl)else:self.pathentry.set_text(self.origurl)self.repo=hg.repository(self.ui,path=self.repo.root)self.graphview.set_repo(self.repo,self.stbar)self.changeview.set_repo(self.repo)self.changeview.bfile=Noneifhasattr(self,'mqwidget'):self.mqwidget.set_repo(self.repo)ifresettip:self.origtip=len(self.repo)self.reload_log()self.toolbar.remove(self.toolbar.get_nth_item(0))self.toolbar.remove(self.toolbar.get_nth_item(0))self.cmd_set_sensitive('accept',False)self.cmd_set_sensitive('reject',False)self.syncbox.set_enable('bundle',False)forwinself.incoming_disabled:w.set_sensitive(True)forcmdinself.incoming_disabled_cmds:self.cmd_set_sensitive(cmd,True)self.stbar.set_idle_text(None)defreject_clicked(self,button):self.remove_overlay(False)defincoming_clicked(self,toolbutton):defcleanup():try:shutil.rmtree(self.bundledir)exceptOSError:passpath=hglib.fromutf(self.pathentry.get_text()).strip()ifnotpath:gdialog.Prompt(_('No remote path specified'),_('Please enter or select a remote path'),self).run()self.pathentry.grab_focus()returnifnotself.bundledir:self.bundledir=tempfile.mkdtemp(prefix='thg-incoming-')atexit.register(cleanup)bfile=pathforbadcharin(':','*','\\','?','#'):bfile=bfile.replace(badchar,'')bfile=bfile.replace('/','_')bfile=os.path.join(self.bundledir,bfile)+'.hg'cmdline=['hg','incoming','--bundle',bfile]cmdline+=self.get_proxy_args()cmdline+=[hglib.validate_synch_path(path,self.repo)]defcallback(return_code,*args):self.stbar.end()self.syncbox.set_enable('stop',False)self.cmd_set_sensitive('stop',False)ifreturn_code==0andos.path.isfile(bfile):self.set_bundlefile(bfile)text=_('%d incoming changesets')%self.npreviewselifreturn_codeisNone:text=_('Aborted incoming')else:text=_('No incoming changesets')self.stbar.set_idle_text(text)ifself.runner.execute(cmdline,callback):self.runner.set_title(_('Incoming'))self.stbar.begin(_('Checking incoming changesets...'))self.syncbox.set_enable('stop',True)self.cmd_set_sensitive('stop',True)else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defset_bundlefile(self,bfile,**kwopts):self.origurl=self.urlcombo.get_active()ifself.origurl==-1:self.origurl=self.pathentry.get_text()self.pathentry.set_text(bfile)# create apply/reject toolbar buttonsapply=gtk.ToolButton(gtk.STOCK_APPLY)apply.set_tooltip(self.tooltips,_('Accept incoming previewed changesets'))apply.set_label(_('Accept'))apply.show()reject=gtk.ToolButton(gtk.STOCK_DIALOG_ERROR)reject.set_tooltip(self.tooltips,_('Reject incoming previewed changesets'))reject.set_label(_('Reject'))reject.show()apply.connect('clicked',self.apply_clicked)reject.connect('clicked',self.reject_clicked)self.toolbar.insert(reject,0)self.toolbar.insert(apply,0)self.cmd_set_sensitive('accept',True)self.cmd_set_sensitive('reject',True)cmds=('incoming','outgoing','push','pull','email','refresh','synchronize','mq','add-bundle')self.incoming_disabled_cmds=[]forcmdincmds:self.cmd_set_sensitive(cmd,False)self.incoming_disabled_cmds.append(cmd)ignore=(self.syncbar_apply,self.syncbar_reject,self.ppullbox,self.stop_button)self.incoming_disabled=[]defdisable_child(w):if(wnotinignore)andw.get_property('sensitive'):w.set_sensitive(False)self.incoming_disabled.append(w)self.syncbox.foreach(disable_child)self.syncbox.set_enable('bundle',True)self.bfile=bfileoldtip=len(self.repo)self.repo=hg.repository(self.ui,path=bfile)self.graphview.set_repo(self.repo,self.stbar)self.changeview.set_repo(self.repo)self.changeview.bfile=bfileifhasattr(self,'mqwidget'):self.mqwidget.set_repo(self.repo)self.npreviews=len(self.repo)-oldtipself.reload_log(**kwopts)self.stbar.set_idle_text(_('Bundle Preview'))self.bundle_autoreject=Falsedefadd_bundle_clicked(self,button):result=gtklib.NativeSaveFileDialogWrapper(title=_('Open Bundle'),open=True).run()ifresult:self.set_bundlefile(result)defpull_clicked(self,toolbutton):combo=self.ppullcombolist,iter=combo.get_model(),combo.get_active_iter()ppull,label=list[list.get_path(iter)]ifppull=='fetch':cmd=['fetch','--message','merge']# load the fetch extension explicitlyhglib.loadextension(self.ui,'fetch')else:cmd=['pull']ifppull=='update':cmd.append('--update')elifppull=='rebase':cmd.append('--rebase')# load the rebase extension explicitlyhglib.loadextension(self.ui,'rebase')path=hglib.fromutf(self.pathentry.get_text()).strip()remote_path=hglib.validate_synch_path(path,self.repo)ifnotpath:gdialog.Prompt(_('No remote path specified'),_('Please enter or select a remote path'),self).run()self.pathentry.grab_focus()returncmdline=['hg']+cmd+self.get_proxy_args()+[remote_path]defcallback(return_code,*args):self.stbar.end()self.syncbox.set_enable('stop',False)self.cmd_set_sensitive('stop',False)ifreturn_code==0:self.repo.invalidate()self.changeview.clear_cache()if'--rebase'incmd:self.origtip=len(self.repo)self.reload_log()text=_('Finished pull with rebase')eliflen(self.repo)>self.origtip:self.reload_log()text=_('Finished pull')else:text=_('No changesets to pull')else:text=_('Aborted pull')self.stbar.set_idle_text(text)ifself.runner.execute(cmdline,callback):self.runner.set_title(_('Pull'))self.stbar.begin(_('Pulling changesets...'))self.syncbox.set_enable('stop',True)self.cmd_set_sensitive('stop',True)else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defoutgoing_clicked(self,toolbutton):path=hglib.fromutf(self.pathentry.get_text()).strip()ifnotpath:gdialog.Prompt(_('No remote path specified'),_('Please enter or select a remote path'),self).run()self.pathentry.grab_focus()returncmd=['hg','outgoing','--quiet','--template','{node}\n']cmd+=self.get_proxy_args()cmd+=[hglib.validate_synch_path(path,self.repo)]defcallback(return_code,buffer,*args):self.stbar.end()self.syncbox.set_enable('stop',False)self.cmd_set_sensitive('stop',False)ifreturn_code==0:outgoing=[]forlineinbuffer.splitlines()[:-1]:try:node=self.repo[line].node()outgoing.append(node)except:passself.outgoing=outgoingself.reload_log()text=_('%d outgoing changesets')%len(outgoing)elifreturn_codeisNone:text=_('Aborted outgoing')else:text=_('No outgoing changesets')self.stbar.set_idle_text(text)ifself.runner.execute(cmd,callback):self.runner.set_title(_('Outgoing'))self.stbar.begin(_('Checking outgoing changesets...'))self.syncbox.set_enable('stop',True)self.cmd_set_sensitive('stop',True)else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defemail_clicked(self,toolbutton):path=hglib.fromutf(self.pathentry.get_text()).strip()ifnotpath:gdialog.Prompt(_('No repository selected'),_('Select a peer repository to compare with'),self).run()self.pathentry.grab_focus()returnopts=['--outgoing',path]dlg=hgemail.EmailDialog(self.repo.root,opts)self.show_dialog(dlg)defpush_clicked(self,toolbutton):original_path=hglib.fromutf(self.pathentry.get_text()).strip()remote_path=hglib.validate_synch_path(original_path,self.repo)ifnotremote_path:gdialog.Prompt(_('No remote path specified'),_('Please enter or select a remote path'),self).run()self.pathentry.grab_focus()returnconfirm_push=Falseifnothg.islocal(remote_path):ifself.forcepush:title=_('Confirm Forced Push to Remote Repository')text=_('Forced push to remote repository\n%s\n''(creating new heads in remote if needed)?')%original_pathbuttontext=_('Forced &Push')else:title=_('Confirm Push to remote Repository')text=_('Push to remote repository\n%s\n?')%original_pathbuttontext=_('&Push')confirm_push=Trueelifself.forcepush:title=_('Confirm Forced Push')text=_('Forced push to repository\n%s\n''(creating new heads if needed)?')%original_pathbuttontext=_('Forced &Push')confirm_push=Trueifconfirm_push:dlg=gdialog.CustomPrompt(title,text,None,(buttontext,_('&Cancel')),default=1,esc=1)ifdlg.run()!=0:returncmdline=['hg','push']+self.get_proxy_args()ifself.forcepush:cmdline+=['--force']cmdline+=[remote_path]defcallback(return_code,*args):self.stbar.end()self.syncbox.set_enable('stop',False)self.cmd_set_sensitive('stop',False)ifreturn_code==0:ifself.outgoing:self.outgoing=[]self.reload_log()text=_('Finished push')else:text=_('Aborted push')self.stbar.set_idle_text(text)ifself.runner.execute(cmdline,callback):self.runner.set_title(_('Push'))self.stbar.begin(_('Pushing changesets...'))self.syncbox.set_enable('stop',True)self.cmd_set_sensitive('stop',True)else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defconf_clicked(self,toolbutton,combo):newpath=hglib.fromutf(self.pathentry.get_text()).strip()foralias,pathinself.repo.ui.configitems('paths'):ifnewpathin(path,url.hidepassword(path)):newpath=Nonebreakdlg=thgconfig.ConfigDialog(True)dlg.show_all()ifnewpath:dlg.new_path(newpath,'default')else:dlg.focus_field('tortoisehg.postpull')dlg.run()dlg.hide()self.refreshui()self.update_urllist()self.update_postpull()defstop_clicked(self,toolbutton):self.runner.stop()defimport_clicked(self,toolbutton):oldlen=len(self.repo)enabled=hasattr(self,'mqpaned')ifenabled:oldnum=self.mqwidget.get_num_patches()defimport_completed():hglib.invalidaterepo(self.repo)self.changeview.clear()ifoldlen<len(self.repo):self.reload_log()ifenabledandoldnum<self.mqwidget.get_num_patches():self.mqwidget.refresh()self.enable_mqpanel(enable=True)dialog=thgimport.ImportDialog(self.repo)dialog.set_notify_func(import_completed)self.show_dialog(dialog)defupdate_urllist(self):urllist=self.urlcombo.get_model()urllist.clear()foralias,pathinself.repo.ui.configitems('paths'):path=url.hidepassword(path)urllist.append((hglib.toutf(path),hglib.toutf(alias)))ifalias=='default':self.urlcombo.set_active(len(urllist)-1)defupdate_postpull(self,ppull=None):ifppullisNone:ppull=self.repo.ui.config('tortoisehg','postpull','none')forrowinself.ppullcombo.get_model():name,label=rowifname==ppull:self.ppullcombo.set_active_iter(row.iter)breakdefrealize_settings(self):self.vpaned.set_position(self.setting_vpos)self.hpaned.set_position(self.setting_hpos)ifhasattr(self,'mqpaned')andself.mqtb.get_active():self.mqpaned.set_position(self.setting_mqhpos)defthgdiff(self,treeview):'ctrl-d handler'self.vdiff_change(None)defthgparent(self,treeview):'ctrl-p handler'parent=self.repo['.'].rev()self.graphview.set_revision_id(parent)defthgnavigate(self,treeview):'ctrl-g handler'self.show_goto_dialog()defget_live_branches(self):live=[]dblist=self.repo.ui.config('tortoisehg','deadbranch','')deadbranches=[x.strip()forxindblist.split(',')]fornameinself.repo.branchtags().keys():ifnamenotindeadbranches:live.append(name)returnlivedefselect_branch(self,combo):row=combo.get_active()ifrow==0:ifself.lastbranchrow:combo.set_active(self.lastbranchrow)elifrow!=self.lastbranchrow:self.filter='branch'self.lastbranchrow=rowself.branchbutton.set_active(True)self.branchbutton.set_sensitive(True)self.reload_log(branch=combo.get_model()[row][0])defshow_goto_dialog(self):'Launch a modeless goto revision dialog'defgoto_rev_(rev):self.goto_rev(rev)defresponse_(dialog,response_id):dialog.hide()defdelete_event(dialog,event,data=None):# return True to prevent the dialog from being destroyedreturnTruedlg=gorev.GotoRevDialog(goto_rev_)dlg.connect('response',response_)dlg.connect('delete-event',delete_event)dlg.set_modal(False)dlg.show()self.gorev_dialog=dlgdefgoto_rev(self,revision):rid=self.repo[revision].rev()self.graphview.set_revision_id(rid,load=True)defstrip_rev(self,menuitem):defstrip_completed():self.repo.invalidate()self.reload_log()self.changeview.clear()rev=self.curreviddialog=thgstrip.StripDialog(rev)dialog.set_notify_func(strip_completed)self.show_dialog(dialog)defshow_dialog(self,dlg):dlg.set_transient_for(self)dlg.show_all()dlg.present()ifgtk.pygtk_version<(2,12,0):# Workaround for old PyGTK (< 2.12.0) issue.# See background of this: f668034aeda3dlg.set_transient_for(None)defbackout_rev(self,menuitem):oldlen=len(self.repo)hash=str(self.repo[self.currevid])parents=[x.node()forxinself.repo.parents()]defcinotify(dlg):'User comitted the merge'dlg.ready=Falsedlg.hide()self.reload_log()defrefresh(*args):self.repo.invalidate()self.changeview.clear_cache()iflen(self.repo)!=oldlen:self.reload_log()iflen(self.repo.parents())!=len(parents):# User auto-merged the backoutfromtortoisehg.hgtkimportcommitdlg=commit.run(ui.ui())dlg.set_transient_for(self)dlg.set_modal(True)dlg.set_notify_func(cinotify,dlg)dlg.display()dlg=backout.BackoutDialog(hash)dlg.connect('destroy',refresh)self.show_dialog(dlg)defrevert(self,menuitem):rev=self.currevidres=gdialog.Confirm(_('Confirm Revert All Files'),[],self,_('Revert all files to revision %d?\nThis will overwrite your ''local changes')%rev).run()ifres!=gtk.RESPONSE_YES:returncmdline=['hg','revert','--verbose','--all','--rev',str(rev)]dlg=hgcmd.CmdDialog(cmdline)dlg.show_all()dlg.run()dlg.hide()defvdiff_change(self,menuitem,pats=[]):ifself.currevidisNone:returnrev=self.currevidopts={'change':str(rev),'bundle':self.bfile}parents=self.repo[rev].parents()iflen(parents)==2:ifself.changeview.diff_other_parent():parent=parents[1].rev()else:parent=parents[0].rev()opts['rev']=[str(parent),str(rev)]self._do_diff(pats,opts)defvdiff_local(self,menuitem,pats=[]):opts={'rev':[str(self.currevid)],'bundle':self.bfile}self._do_diff(pats,{'rev':[str(self.currevid)]})defdiff_revs(self,menuitem):rev0,rev1=self.revrangestatopts=self.merge_opts(commands.table['^status|st'][1],('include','exclude','git'))statopts['rev']=['%u:%u'%(rev0,rev1)]statopts['modified']=Truestatopts['added']=Truestatopts['removed']=Truedialog=status.GStatus(self.ui,self.repo,self.cwd,self.pats,statopts)dialog.display()returnTruedefvdiff_selected(self,menuitem):strrevs=[str(r)forrinself.revrange]self._do_diff(self.pats,{'rev':strrevs})defemail_revs(self,menuitem):revrange=list(self.revrange)revrange.sort()opts=['--rev',str(revrange[0])+':'+str(revrange[1])]dlg=hgemail.EmailDialog(self.repo.root,opts)self.show_dialog(dlg)defexport_revs(self,menuitem):result=gtklib.NativeFolderSelectDialog(title=_('Save patches to'),initial=self.repo.root).run()ifresult:revs=list(self.revrange)revs.sort()rev='%d:%d'%(revs[0],revs[1])# In case new export args are added in the future, merge the# hg defaultsopts=self.merge_opts(commands.table['^export'][1],())opts['output']=os.path.join(result,'%b_rev%R.patch')defdohgexport():commands.export(self.ui,self.repo,rev,**opts)s,o=self._hg_call_wrapper('Export',dohgexport,False)defbundle_revs(self,menuitem):revrange=list(self.revrange)revrange.sort()parent=self.repo[revrange[0]].parents()[0].rev()# Special case for revision 0's parent.ifparent==-1:parent='null'filename="%s_rev%d_to_rev%s.hg"%(os.path.basename(self.repo.root),revrange[0],revrange[1])result=gtklib.NativeSaveFileDialogWrapper(title=_('Write bundle to'),initial=self.repo.root,filename=filename).run()ifresult:cmdline=['hg','bundle','--base',str(parent),'--rev',str(revrange[1]),result]dlg=hgcmd.CmdDialog(cmdline)dlg.show_all()dlg.run()dlg.hide()defqimport_rev(self,menuitem):"""QImport selected revision."""rev=str(self.currevid)self.qimport_revs(menuitem,rev)defqimport_revs(self,menuitem,rev=None):"""QImport revision range."""ifrev==None:revs=list(self.revrange)revs.sort()rev='%s:%s'%(str(revs[0]),str(revs[1]))cmdline=['hg','qimport','--rev',rev]dialog=hgcmd.CmdDialog(cmdline)dialog.show_all()dialog.run()dialog.hide()self.repo.invalidate()self.reload_log()self.changeview.clear()self.enable_mqpanel()defrebase_selected(self,menuitem):"""Rebase revision on top of selection (1st on top of 2nd)."""revs=self.revrangeres=gdialog.Confirm(_('Confirm Rebase Revision'),[],self,_('Rebase revision %d on top of %d?')%(revs[0],revs[1])).run()ifres!=gtk.RESPONSE_YES:returncmdline=['hg','rebase','--source',str(revs[0]),'--dest',str(revs[1])]dialog=hgcmd.CmdDialog(cmdline)dialog.show_all()dialog.run()dialog.hide()self.repo.invalidate()self.reload_log()self.changeview.clear()deftransplant_revs(self,menuitem):"""Transplant revision range on top of current revision."""revs=list(self.revrange)revs.sort()cmdline=['hg','transplant','%d:%d'%(revs[0],revs[1])]dialog=hgcmd.CmdDialog(cmdline)dialog.show_all()dialog.run()dialog.hide()self.repo.invalidate()self.reload_log()self.changeview.clear()defget_rev_tag(self,rev,include=None,exclude=None):fortaginself.repo.nodetags(self.repo[rev].node()):iftag!='tip' \
and((notinclude)or(includeandtagininclude)) \
and((notexclude)or(excludeandtagnotinexclude)):returntagreturn''defadd_tag(self,menuitem):# save tag info for detecting new tags addedbmarks=hglib.get_repo_bookmarks(self.repo)oldtags=self.repo.tagslist()oldlen=len(self.repo)rev=str(self.currevid)tag=self.get_rev_tag(rev,exclude=bmarks)defrefresh(*args):self.refresh_on_marker_change(oldlen,oldtags,self.repo.tagslist())dialog=tagadd.TagAddDialog(self.repo,tag,rev)dialog.connect('destroy',refresh)self.show_dialog(dialog)defadd_bookmark(self,menuitem):# save bookmark info for detecting new bookmarks added# since we can now move bookmarks, need to store# the associated changesets as welloldbookmarks=hglib.get_repo_bookmarks(self.repo,values=True)oldlen=len(self.repo)rev=str(self.currevid)bmark=self.get_rev_tag(rev,include=oldbookmarks)defrefresh(*args):self.refresh_on_marker_change(oldlen,oldbookmarks,hglib.get_repo_bookmarks(self.repo,values=True))dialog=bookmark.BookmarkDialog(self.repo,bookmark.TYPE_ADDREMOVE,bmark,rev)dialog.connect('destroy',refresh)self.show_dialog(dialog)defrename_bookmark(self,menuitem):# save bookmark info for detecting bookmarks renamedoldbookmarks=hglib.get_repo_bookmarks(self.repo)oldlen=len(self.repo)rev=str(self.currevid)bmark=self.get_rev_tag(rev,include=oldbookmarks)defrefresh(*args):self.refresh_on_marker_change(oldlen,oldbookmarks,hglib.get_repo_bookmarks(self.repo))dialog=bookmark.BookmarkDialog(self.repo,bookmark.TYPE_RENAME,bmark,rev)dialog.connect('destroy',refresh)self.show_dialog(dialog)defcurrent_bookmark(self,menuitem):# save current bookmark info for detecting current bookmark changedbookmarks=extensions.find('bookmarks')# Note that the dialog shouldn't change the repo len, or # of bookmarks,# etc, but check in case they've been modified by something else...oldbookmarks=hglib.get_repo_bookmarks(self.repo)oldlen=len(self.repo)oldcurrent=hglib.get_repo_bookmarkcurrent(self.repo)rev=str(self.currevid)bmark=self.get_rev_tag(rev,include=oldbookmarks)defrefresh(*args):self.refresh_on_current_marker_change(oldlen,oldbookmarks,oldcurrent,hglib.get_repo_bookmarks(self.repo),hglib.get_repo_bookmarkcurrent(self.repo))dialog=bookmark.BookmarkDialog(self.repo,bookmark.TYPE_CURRENT,bmark,rev)dialog.connect('destroy',refresh)self.show_dialog(dialog)defbisect_reset(self,menuitem):commands.bisect(ui=self.ui,repo=self.repo,good=False,bad=False,skip=False,reset=True)defbisect_good(self,menuitem):cmd=['hg','bisect','--good',str(self.currevid)]dlg=hgcmd.CmdDialog(cmd)dlg.show_all()dlg.run()dlg.hide()self.refresh_model()defbisect_bad(self,menuitem):cmd=['hg','bisect','--bad',str(self.currevid)]dlg=hgcmd.CmdDialog(cmd)dlg.show_all()dlg.run()dlg.hide()self.refresh_model()defbisect_skip(self,menuitem):cmd=['hg','bisect','--skip',str(self.currevid)]dlg=hgcmd.CmdDialog(cmd)dlg.show_all()dlg.run()dlg.hide()self.refresh_model()defshow_status(self,menuitem):rev=self.currevidstatopts=self.merge_opts(commands.table['^status|st'][1],('include','exclude','git'))ifself.changeview.diff_other_parent():parent=self.repo[rev].parents()[1].rev()else:parent=self.repo[rev].parents()[0].rev()statopts['rev']=[str(parent),str(rev)]statopts['modified']=Truestatopts['added']=Truestatopts['removed']=Truedialog=status.GStatus(self.ui,self.repo,self.cwd,self.pats,statopts)dialog.display()defpush_to(self,menuitem):remote_path=hglib.fromutf(self.pathentry.get_text()).strip()foralias,pathinself.repo.ui.configitems('paths'):ifremote_path==alias:remote_path=pathelifremote_path==url.hidepassword(path):remote_path=pathifnotremote_path:gdialog.Prompt(_('No remote path specified'),_('Please enter or select a remote path'),self).run()self.pathentry.grab_focus()returnnode=self.repo[self.currevid].node()rev=str(self.currevid)cmdline=['hg','push','--rev',rev,remote_path]defcallback(return_code,*args):self.stbar.end()self.syncbox.set_enable('stop',False)self.cmd_set_sensitive('stop',False)ifreturn_code==0:ifself.outgoing:d=self.outgoing.index(node)self.outgoing=self.outgoing[d+1:]self.reload_log()text=_('Finished push to revision %s')%revelse:text=_('Aborted push')self.stbar.set_idle_text(text)ifself.runner.execute(cmdline,callback):self.runner.set_title(_('Push to %s')%rev)self.stbar.begin(_('Pushing changesets to revision %s...')%rev)self.syncbox.set_enable('stop',True)self.cmd_set_sensitive('stop',True)else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defpull_to(self,menuitem):rev=str(self.currevid)cmdline=['hg','pull','--rev',rev,self.bfile]defcallback(return_code,*args):self.stbar.end()self.syncbox.set_enable('stop',False)self.cmd_set_sensitive('stop',False)ifreturn_code==0:curtip=len(hg.repository(self.ui,self.repo.root))self.repo=hg.repository(self.ui,path=self.bfile)self.graphview.set_repo(self.repo,self.stbar)self.changeview.set_repo(self.repo)ifhasattr(self,'mqwidget'):self.mqwidget.set_repo(self.repo)self.npreviews=len(self.repo)-curtipifself.npreviews==0:self.remove_overlay(False)else:self.reload_log()text=_('Finished pull to revision %s')%revelse:text=_('Aborted pull')self.stbar.set_idle_text(text)ifself.runner.execute(cmdline,callback):self.runner.set_title(_('Pull to %s')%rev)self.stbar.begin(_('Pulling changesets to revision %s...')%rev)self.syncbox.set_enable('stop',True)self.cmd_set_sensitive('stop',True)else:gdialog.Prompt(_('Cannot run now'),_('Please try again after running ''operation is completed'),self).run()defcopy_hash(self,menuitem):hash=self.repo[self.currevid].hex()clipboard=gtk.Clipboard()clipboard.set_text(hash)defexport_patch(self,menuitem):rev=self.currevidfilename="%s_rev%s.patch"%(os.path.basename(self.repo.root),rev)result=gtklib.NativeSaveFileDialogWrapper(title=_('Save patch to'),initial=self.repo.root,filename=filename).run()ifresult:# In case new export args are added in the future, merge the# hg defaultsexportOpts=self.merge_opts(commands.table['^export'][1],())exportOpts['output']=resultdefdohgexport():commands.export(self.ui,self.repo,str(rev),**exportOpts)success,outtext=self._hg_call_wrapper("Export",dohgexport,False)defbundle_rev_to_tip(self,menuitem):try:rev=self.currevidparent=self.repo[rev].parents()[0].rev()# Special case for revision 0's parent.ifparent==-1:parent='null'except(ValueError,error.LookupError):returnfilename="%s_rev%d_to_tip.hg"%(os.path.basename(self.repo.root),rev)result=gtklib.NativeSaveFileDialogWrapper(title=_('Write bundle to'),initial=self.repo.root,filename=filename).run()ifresult:cmdline=['hg','bundle','--base',str(parent),result]dlg=hgcmd.CmdDialog(cmdline)dlg.show_all()dlg.run()dlg.hide()defemail_patch(self,menuitem):rev=self.curreviddlg=hgemail.EmailDialog(self.repo.root,['--rev',str(rev)])self.show_dialog(dlg)defcheckout(self,menuitem):rev=self.currevidparents=[x.node()forxinself.repo.parents()]dialog=update.UpdateDialog(rev)dialog.set_notify_func(self.checkout_completed,parents)self.show_dialog(dialog)defcheckout_completed(self,oldparents):self.repo.invalidate()self.repo.dirstate.invalidate()self.changeview.clear_cache()newparents=[x.node()forxinself.repo.parents()]ifnotoldparents==newparents:self.refresh_model()defdomerge(self,menuitem):defmerge_notify(args):oldparents,repolen=argsself.repo.invalidate()self.repo.dirstate.invalidate()self.changeview.clear_cache()iflen(self.repo)!=repolen:self.reload_log()elifnotoldparents==self.repo.parents():self.refresh_model()# update parents for the next notifyingargs[0]=self.repo.parents()ifself.revrange:rev0,rev1=self.revrangeelse:rev0,rev1=self.repo['.'].rev(),self.currevidargs=[self.repo.parents(),len(self.repo)]dlg=merge.MergeDialog(rev0,rev1)dlg.set_notify_func(merge_notify,*args)merge_notify(args)# could have immediately switched parentsself.show_dialog(dlg)defarchive(self,menuitem):rev=self.currevidparents=[x.node()forxinself.repo.parents()]dlg=archive.ArchiveDialog(rev)self.show_dialog(dlg)deftransplant_rev(self,menuitem):"""Transplant selection on top of current revision."""rev=self.currevidcmdline=['hg','transplant',str(rev)]dialog=hgcmd.CmdDialog(cmdline)dialog.show_all()dialog.run()dialog.hide()self.repo.invalidate()self.reload_log()self.changeview.clear()defselect_common_ancestor(self,menuitem):rev1,rev2=self.revrangechangelog=self.repo.changeloglookup=self.repo.lookupancestor=changelog.ancestor(lookup(rev1),lookup(rev2))rev=changelog.rev(ancestor)self.goto_rev(rev)self.origsel=Nonedefthgrefresh(self,window):self.reload_log()defrefresh_clicked(self,toolbutton,data=None):self.reload_log()returnTruedefenable_mqpanel(self,enable=None):ifnothasattr(self,'mqpaned'):returnifenableisNone:enable=self.setting_mqvisandself.mqwidget.has_patch()# set the state of MQ toolbuttonself.cmd_handler_block_by_func('mq',self.mq_clicked)self.cmd_set_active('mq',enable)self.cmd_handler_unblock_by_func('mq',self.mq_clicked)self.cmd_set_sensitive('mq',self.mqwidget.has_mq())# show/hide MQ paneoldpos=self.mqpaned.get_position()self.mqpaned.set_position(enableandself.setting_mqhposor0)ifnotenableandoldpos:self.setting_mqhpos=oldposdefmq_clicked(self,widget,*args):self.enable_mqpanel(widget.get_active())deftree_button_press(self,tree,event):ifevent.button==3andnot(event.state&(gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK)):path=tree.get_path_at_pos(int(event.x),int(event.y))ifnotpath:returnFalsecrow=path[0](model,pathlist)=tree.get_selection().get_selected_rows()ifpathlist==[]:returnFalsesrow=pathlist[0]ifsrow==crow:self.tree_popup_menu(tree,event.button,event.time)else:tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)tree.get_selection().select_path(crow)self.origsel=srowrev0=self.graphview.get_revid_at_path(srow)rev1=self.graphview.get_revid_at_path(crow)self.revrange=(rev0,rev1)self.tree_popup_menu_diff(tree,event.button,event.time)returnTruereturnFalsedeftree_popup_menu(self,treeview,button=0,time=0):menu=self.tree_context_menu()menu.popup(None,None,None,button,time)returnTruedeftree_popup_menu_diff(self,treeview,button=0,time=0):menu=self.tree_diff_context_menu()menu.popup(None,None,None,button,time)returnTruedeftree_row_act(self,tree,path,column):self.vdiff_change(None)returnTruedefrun(ui,*pats,**opts):cmdoptions={'follow':False,'follow-first':False,'copies':False,'keyword':[],'limit':0,'rev':[],'removed':False,'no_merges':False,'date':None,'only_merges':None,'prune':[],'git':False,'verbose':False,'include':[],'exclude':[],'filehist':None,'canonpats':[]}cmdoptions.update(opts)pats=hglib.canonpaths(pats)+cmdoptions['canonpats']returnGLog(ui,None,None,pats,cmdoptions)
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.