Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
## status.py - status dialog for TortoiseHg## Copyright 2007 Brad Schick, brad at gmail . com# Copyright (C) 2007 TK Soh <teekaysoh@gmail.com>#importosimportcStringIOimportpygtkpygtk.require('2.0')importgtkimportpangofrommercurial.i18nimport_frommercurial.nodeimport*frommercurialimportcmdutil,util,ui,hg,commands,patch,mdifffrommercurialimportmergeasmerge_fromshlibimportshell_notifyfromhglibimporttoutf,rootpath,gettabwidthfromgdialogimport*fromdialogimportentry_dialogimporthgshelve# diff_model row enumerationsDM_REJECTED=0DM_CHUNK_TEXT=1DM_NOT_HEADER_CHUNK=2DM_HEADER_CHUNK=3DM_CHUNK_ID=4classGStatus(GDialog):"""GTK+ based dialog for displaying repository status Also provides related operations like add, delete, remove, revert, refresh, ignore, diff, and edit. The following methods are meant to be overridden by subclasses. At this point GCommit is really the only intended subclass. auto_check(self) get_menu_info(self) """### Following methods are meant to be overridden by subclasses ###definit(self):GDialog.init(self)defauto_check(self):ifself.test_opt('check'):forentryinself.model:entry[0]=Trueself._update_check_count()defget_menu_info(self):"""Returns menu info in this order: merge, addrem, unknown, clean, ignored, deleted, unresolved, resolved """return(# merge(('_difference',self._diff_file),('_view right',self._view_file),('view _left',self._view_left_file),('_revert',self._revert_file),('l_og',self._log_file)),# addrem(('_difference',self._diff_file),('_view',self._view_file),('_revert',self._revert_file),('l_og',self._log_file)),# unknown(('_view',self._view_file),('_delete',self._delete_file),('_add',self._add_file),('_ignore',self._ignore_file)),# clean(('_view',self._view_file),('re_move',self._remove_file),('re_name',self._rename_file),('_copy',self._copy_file),('l_og',self._log_file)),# ignored(('_view',self._view_file),('_delete',self._delete_file)),# deleted(('_view',self._view_file),('_revert',self._revert_file),('re_move',self._remove_file),('l_og',self._log_file)),# unresolved(('_difference',self._diff_file),('_view right',self._view_file),('view _left',self._view_left_file),('_revert',self._revert_file),('l_og',self._log_file),('resolve',self._do_resolve),('mark resolved',self._mark_resolved)),# resolved(('_difference',self._diff_file),('_view right',self._view_file),('view _left',self._view_left_file),('_revert',self._revert_file),('l_og',self._log_file),('mark unresolved',self._unmark_resolved)),)### End of overridable methods ###### Overrides of base class methods ###defparse_opts(self):self._ready=False# Determine which files to displayifself.test_opt('all'):forcheckinself._show_checks.values():check.set_active(True)else:set=Falseforoptinself.opts:ifoptinself._show_checksandself.opts[opt]:set=Trueself._show_checks[opt].set_active(True)ifnotset:forcheckin[item[1]foriteminself._show_checks.iteritems()ifitem[0]in('modified','added','removed','deleted','unknown')]:check.set_active(True)defget_title(self):returnos.path.basename(self.repo.root)+' status '+':'.join(self.opts['rev'])+' '+' '.join(self.pats)defget_icon(self):return'menushowchanged.ico'defget_defsize(self):returnself._setting_defsizedefget_tbbuttons(self):tbuttons=[self.make_toolbutton(gtk.STOCK_REFRESH,'Re_fresh',self._refresh_clicked,tip='refresh'),gtk.SeparatorToolItem()]ifself.count_revs()<2:tbuttons+=[self.make_toolbutton(gtk.STOCK_MEDIA_REWIND,'Re_vert',self._revert_clicked,tip='revert'),self.make_toolbutton(gtk.STOCK_ADD,'_Add',self._add_clicked,tip='add'),self.make_toolbutton(gtk.STOCK_JUMP_TO,'Move',self._move_clicked,tip='move selected files to other directory'),self.make_toolbutton(gtk.STOCK_DELETE,'_Remove',self._remove_clicked,tip='remove'),gtk.SeparatorToolItem()]self.showdiff_toggle=gtk.ToggleToolButton(gtk.STOCK_JUSTIFY_FILL)self.showdiff_toggle.set_use_underline(True)self.showdiff_toggle.set_label('_Show Diff')self.showdiff_toggle.set_tooltip(self.tooltips,'show diff pane')self.showdiff_toggle.set_active(False)self._showdiff_toggled_id=self.showdiff_toggle.connect('toggled',self._showdiff_toggled)tbuttons.append(self.showdiff_toggle)self.shelve_btn=self.make_toolbutton(gtk.STOCK_FILE,'Shelve',self._shelve_clicked,tip='set aside selected changes')self.unshelve_btn=self.make_toolbutton(gtk.STOCK_EDIT,'Unshelve',self._unshelve_clicked,tip='restore shelved changes')tbuttons+=[gtk.SeparatorToolItem(),self.shelve_btn,self.unshelve_btn,]returntbuttonsdefsave_settings(self):settings=GDialog.save_settings(self)settings['gstatus']=(self._diffpane.get_position(),self._setting_lastpos)returnsettingsdefload_settings(self,settings):GDialog.load_settings(self,settings)ifsettings:mysettings=settings['gstatus']self._setting_pos=mysettings[0]self._setting_lastpos=mysettings[1]else:self._setting_pos=64000self._setting_lastpos=270self.tabwidth=gettabwidth(self.ui)defget_body(self):self.connect('map-event',self._displayed)# TODO: should generate menus dynamically during right-click, currently# there can be entires that are not always supported or relavant.merge,addrem,unknown,clean,ignored,deleted,unresolved,resolved \
=self.get_menu_info()merge_menu=self.make_menu(merge)addrem_menu=self.make_menu(addrem)unknown_menu=self.make_menu(unknown)clean_menu=self.make_menu(clean)ignored_menu=self.make_menu(ignored)deleted_menu=self.make_menu(deleted)resolved_menu=self.make_menu(resolved)unresolved_menu=self.make_menu(unresolved)# Dictionary with a key of file-stat and values containing context-menusself._menus={}self._menus['M']=merge_menuself._menus['A']=addrem_menuself._menus['R']=addrem_menuself._menus['?']=unknown_menuself._menus['C']=clean_menuself._menus['I']=ignored_menuself._menus['!']=deleted_menuself._menus['MR']=resolved_menuself._menus['MU']=unresolved_menu# model stores the file list.# model[0] = file checked (marked for commit)# model[1] = changetype char# model[2] = file path as UTF-8# model[3] = file pathself.model=gtk.ListStore(bool,str,str,str,str)self.model.set_sort_func(1001,self._sort_by_stat)self.model.set_default_sort_func(self._sort_by_stat)self.tree=gtk.TreeView(self.model)self.tree.connect('button-press-event',self._tree_button_press)self.tree.connect('button-release-event',self._tree_button_release)self.tree.connect('popup-menu',self._tree_popup_menu)self.tree.connect('row-activated',self._tree_row_act)self.tree.connect('key-press-event',self._tree_key_press)self.tree.set_reorderable(False)self.tree.set_enable_search(True)self.tree.set_search_column(2)self.tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)self.tree.get_selection().connect('changed',self._tree_selection_changed,False)ifhasattr(self.tree,'set_rubber_banding'):self.tree.set_rubber_banding(True)self.tree.modify_font(pango.FontDescription(self.fontlist))self.tree.set_headers_clickable(True)toggle_cell=gtk.CellRendererToggle()toggle_cell.connect('toggled',self._select_toggle)toggle_cell.set_property('activatable',True)path_cell=gtk.CellRendererText()stat_cell=gtk.CellRendererText()ifself.count_revs()<2:col0=gtk.TreeViewColumn('',toggle_cell)col0.add_attribute(toggle_cell,'active',0)#col0.set_sort_column_id(0)col0.set_resizable(False)self.tree.append_column(col0)self.selcb=self._add_header_checkbox(col0,self._sel_clicked)col1=gtk.TreeViewColumn('st',stat_cell)col1.add_attribute(stat_cell,'text',1)col1.set_cell_data_func(stat_cell,self._text_color)col1.set_sort_column_id(1001)col1.set_resizable(False)self.tree.append_column(col1)col=gtk.TreeViewColumn('ms',stat_cell)col.add_attribute(stat_cell,'text',4)#col.set_cell_data_func(stat_cell, self._text_color)col.set_sort_column_id(4)col.set_resizable(False)self.tree.append_column(col)col2=gtk.TreeViewColumn('path',path_cell)col2.add_attribute(path_cell,'text',2)col2.set_cell_data_func(path_cell,self._text_color)col2.set_sort_column_id(2)col2.set_resizable(True)self.tree.append_column(col2)scroller=gtk.ScrolledWindow()scroller.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)scroller.add(self.tree)tree_frame=gtk.Frame()tree_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)tree_frame.add(scroller)diff_frame=gtk.Frame()diff_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)scroller=gtk.ScrolledWindow()scroller.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)diff_frame.add(scroller)# use treeview to diff hunksself.diff_model=gtk.ListStore(bool,str,bool,bool,int)self.diff_tree=gtk.TreeView(self.diff_model)self.diff_tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)self.diff_tree.modify_font(pango.FontDescription(self.fontlist))self.diff_tree.set_property('enable-grid-lines',True)self.diff_tree.connect('row-activated',self._diff_tree_row_act)self.diff_tree.connect('button-press-event',self._diff_tree_button_press)self.diff_tree.set_enable_search(False)diff_hunk_cell=gtk.CellRendererText()diff_hunk_cell.set_property('cell-background','#EEEEEE')diffcol=gtk.TreeViewColumn('diff',diff_hunk_cell,strikethrough=DM_REJECTED,markup=DM_CHUNK_TEXT,strikethrough_set=DM_NOT_HEADER_CHUNK,cell_background_set=DM_HEADER_CHUNK)diffcol.set_resizable(True)self.diff_tree.append_column(diffcol)scroller.add(self.diff_tree)ifself.diffbottom:self._diffpane=gtk.VPaned()else:self._diffpane=gtk.HPaned()self._diffpane.pack1(tree_frame,True,False)self._diffpane.pack2(diff_frame,True,True)self._diffpane.set_position(self._setting_pos)self._diffpane_moved_id=self._diffpane.connect('notify::position',self._diffpane_moved)self.tree.set_headers_clickable(True)returnself._diffpanedefget_extras(self):table=gtk.Table(rows=2,columns=3)table.set_col_spacings(8)self._show_checks={}row,col=0,0checks=('modified','added','removed')ifself.count_revs()<=1:checks+=('deleted','unknown','clean','ignored')fortypeinchecks:check=gtk.CheckButton('_'+type)check.connect('toggled',self._show_toggle,type)table.attach(check,col,col+1,row,row+1)self._show_checks[type]=checkcol+=rowrow=notrowself.counter=gtk.Label('')self.counter.set_alignment(1.0,0.0)# right uphbox=gtk.HBox()hbox.pack_start(table,expand=False)hbox.pack_end(self.counter,expand=True,padding=2)returnhboxdef_add_header_checkbox(self,col,post=None,pre=None,toggle=False):defcbclick(hdr,cb):state=cb.get_active()ifpre:pre(state)iftoggle:cb.set_active(notstate)ifpost:post(notstate)cb=gtk.CheckButton(col.get_title())cb.show()col.set_widget(cb)wgt=cb.get_parent()whilewgt:iftype(wgt)==gtk.Button:wgt.connect("clicked",cbclick,cb)returncbwgt=wgt.get_parent()print"Warning: checkbox action not connected"returndef_update_check_count(self):file_count=0check_count=0forrowinself.model:file_count=file_count+1ifrow[0]:check_count=check_count+1self.counter.set_text(_('%d selected, %d total')%(check_count,file_count))self.selcb.set_active(file_countandfile_count==check_count)defprepare_display(self):self._ready=Trueself._last_files=[]# If the status load failed, no reason to continueifnotself.reload_status():raiseutil.Abort('could not load status')self.auto_check()def_displayed(self,widget,event):self._diffpane_moved(self._diffpane)returnFalsedefshould_live(self,widget=None,event=None):returnFalse### End of overrides ###def_do_reload_status(self):"""Clear out the existing ListStore model and reload it from the repository status. Also recheck and reselect files that remain in the list. """self.repo.dirstate.invalidate()self.repo.invalidate()# The following code was copied from the status function in mercurial\commands.py# and modified slightly to work here# node2 is None (the working dir) when 0 or 1 rev is specificedself._node1,self._node2=cmdutil.revpair(self.repo,self.opts.get('rev'))matcher=cmdutil.match(self.repo,self.pats,self.opts)cwd=(self.patsandself.repo.getcwd())or''status=[nforninself.repo.status(node1=self._node1,node2=self._node2,match=matcher,ignored=self.test_opt('ignored'),clean=self.test_opt('clean'),unknown=self.test_opt('unknown'))](modified,added,removed,deleted,unknown,ignored,clean)=statusself.modified=modifiedchangetypes=(('modified','M',modified),('added','A',added),('removed','R',removed),('deleted','!',deleted),('unknown','?',unknown),('ignored','I',ignored))explicit_changetypes=changetypes+(('clean','C',clean),)# List of the currently checked and selected files to pass on to the new datarecheck=[entry[2]forentryinself.modelifentry[0]]reselect=[self.model[iter][2]foriterinself.tree.get_selection().get_selected_rows()[1]]# merge-state of filesms=merge_.mergestate(self.repo)# Load the new data into the tree's modelself.tree.hide()self.model.clear()foropt,char,changesin([ctforctinexplicit_changetypesifself.test_opt(ct[0])]orchangetypes):forfileinchanges:mst=fileinmsandms[file].upper()or""file=util.localpath(file)checked=fileinrecheckorcharin'MAR'self.model.append([checked,char,toutf(file),file,mst])self._update_check_count()selection=self.tree.get_selection()selected=Falseforrowinself.model:ifrow[2]inreselect:selection.select_iter(row.iter)selected=Trueifnotselected:selection.select_path((0,))files=[row[3]forrowinself.model] self._show_diff_hunks(files)
self.tree.show()
- self.tree.grab_focus()
+ if hasattr(self, 'text'):+ self.text.grab_focus()+ else:+ self.tree.grab_focus()
return True
defreload_status(self):ifnotself._ready:returnFalsesuccess,outtext=self._hg_call_wrapper('Status',self._do_reload_status)returnsuccessdefmake_menu(self,entries):menu=gtk.Menu()forentryinentries:menu.append(self._make_menuitem(entry[0],entry[1]))menu.show_all()returnmenudef_make_menuitem(self,label,handler):menuitem=gtk.MenuItem(label,True)menuitem.connect('activate',self._context_menu_act,handler)menuitem.set_border_width(1)returnmenuitemdef_select_toggle(self,cellrenderer,path):self.model[path][0]=notself.model[path][0]self._update_check_count()returnTruedef_show_toggle(self,check,type):self.opts[type]=check.get_active()self.reload_status()returnTruedef_sort_by_stat(self,model,iter1,iter2):order='MAR!?IC'lhs,rhs=(model.get_value(iter1,1),model.get_value(iter2,1))# GTK+ bug that calls sort before a full row is inserted causing values to be None.# When this happens, just return any value since the call is irrelevant and will be# followed by another with the correct (non-None) valueifNonein(lhs,rhs):return0result=order.find(lhs)-order.find(rhs)returnmin(max(result,-1),1)def_text_color(self,column,text_renderer,list,row_iter):stat=list[row_iter][1]ifstat=='M':text_renderer.set_property('foreground','#000090')elifstat=='A':text_renderer.set_property('foreground','#006400')elifstat=='R':text_renderer.set_property('foreground','#900000')elifstat=='C':text_renderer.set_property('foreground','black')elifstat=='!':text_renderer.set_property('foreground','red')elifstat=='?':text_renderer.set_property('foreground','#AA5000')elifstat=='I':text_renderer.set_property('foreground','black')else:text_renderer.set_property('foreground','black')def_view_left_file(self,stat,file):returnself._view_file(stat,file,True)def_remove_file(self,stat,file):self._hg_remove([file])returnTruedef_rename_file(self,stat,file):fdir,fname=os.path.split(file)newfile=entry_dialog(self,"Rename file to:",True,fname)ifnewfileandnewfile!=fname:self._hg_move([file,os.path.join(fdir,newfile)])returnTruedef_copy_file(self,stat,file):file=self.repo.wjoin(file)fdir,fname=os.path.split(file)dialog=gtk.FileChooserDialog(parent=self,title='Copy file to',action=gtk.FILE_CHOOSER_ACTION_SAVE,buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_COPY,gtk.RESPONSE_OK))dialog.set_default_response(gtk.RESPONSE_OK)dialog.set_current_folder(fdir)dialog.set_current_name(fname)response=dialog.run()newfile=fileifresponse==gtk.RESPONSE_OK:newfile=dialog.get_filename()dialog.destroy()ifnewfile!=file:self._hg_copy([file,newfile])returnTruedef_hg_remove(self,files):wfiles=[self.repo.wjoin(x)forxinfiles]ifself.count_revs()>1:Prompt('Nothing Removed','Remove is not enabled when multiple revisions are specified.',self).run()return# Create new opts, so nothing unintented gets throughremoveopts=self.merge_opts(commands.table['^remove|rm'][1],('include','exclude'))defdohgremove():commands.remove(self.ui,self.repo,*wfiles,**removeopts)success,outtext=self._hg_call_wrapper('Remove',dohgremove)ifsuccess:self.reload_status()def_hg_move(self,files):wfiles=[self.repo.wjoin(x)forxinfiles]ifself.count_revs()>1:Prompt('Nothing Moved','Move is not enabled when ''multiple revisions are specified.',self).run()return# Create new opts, so nothing unintented gets throughmoveopts=self.merge_opts(commands.table['rename|mv'][1],('include','exclude'))defdohgmove():#moveopts['force'] = Truecommands.rename(self.ui,self.repo,*wfiles,**moveopts)success,outtext=self._hg_call_wrapper('Move',dohgmove)ifsuccess:self.reload_status()def_hg_copy(self,files):wfiles=[self.repo.wjoin(x)forxinfiles]ifself.count_revs()>1:Prompt('Nothing Copied','Copy is not enabled when ''multiple revisions are specified.',self).run()return# Create new opts, so nothing unintented gets throughcmdopts=self.merge_opts(commands.table['copy|cp'][1],('include','exclude'))defdohgcopy():commands.copy(self.ui,self.repo,*wfiles,**cmdopts)success,outtext=self._hg_call_wrapper('Copy',dohgcopy)ifsuccess:self.reload_status()def_tree_selection_changed(self,selection,force):# TODO: SJB Zoom diff_tree to first diff of this file#if self.showdiff_toggle.get_active():# files = [self.model[iter][2] for iter in self.tree.get_selection().get_selected_rows()[1]]# if force or files != self._last_files:# self._last_files = files# self._show_diff_hunks(files)returnFalsedef_diff_tree_row_act(self,tree,path,column):# strikethrough-set property seems to be broken, so manually# filter header chunks here using !isheader property of diffmodel.ifself.diff_model[path][DM_NOT_HEADER_CHUNK]:self.diff_model[path][DM_REJECTED]=notself.diff_model[path][DM_REJECTED]def_diff_tree_button_press(self,widget,event):# Used to select all of file patch, will be no longer necessary# activating rows in file list will set/clear all patch parts#if event.button == 1:# tup = widget.get_path_at_pos(int(event.x), int(event.y))# if tup is None:# return False# path = tup[0]# # def get_hunk_pos(filename):# l = []# for n, hunk in enumerate(self._shelve_chunks):# if hunk.filename() == filename:# l.append(n)# return l# # # cliked on header hunk to select/unselect all hunks in file # hunk = self._shelve_chunks[path[0]]# if isinstance(hunk, hgshelve.header):# l = get_hunk_pos(hunk.filename())# selection = self.diff_tree.get_selection()# selected = selection.path_is_selected(path)# for i in l:# if selected:# selection.unselect_path((i,))# else:# selection.select_path((i,))# return True # stop further event handlingreturnFalse# try next handlerdef_show_diff_hunks(self,files):''' Update the diff text '''defmarkup(chunk):importcgihunk=""chunk.seek(0)lines=chunk.readlines()lines[-1]=lines[-1].strip('\n\r')forlineinlines:line=cgi.escape(util.fromlocal(line))ifline.startswith('---')orline.startswith('+++'):hunk+='<span foreground="#000090">%s</span>'%lineelifline.startswith('-'):hunk+='<span foreground="#900000">%s</span>'%lineelifline.startswith('+'):hunk+='<span foreground="#006400">%s</span>'%lineelifline.startswith('@@'):hunk='<span foreground="#FF8000">%s</span>'%lineelse:hunk+=linereturnhunkdefdohgdiff():self.diff_model.clear()try:difftext=[]iflen(files)!=0:wfiles=[self.repo.wjoin(x)forxinfiles]matcher=cmdutil.match(self.repo,wfiles,self.opts)diffopts=mdiff.diffopts(git=True,nodates=True)forsinpatch.diff(self.repo,self._node1,self._node2,match=matcher,opts=diffopts):difftext.extend(s.splitlines(True))difftext=cStringIO.StringIO(''.join(difftext))difftext.seek(0)self._shelve_chunks=hgshelve.parsepatch(difftext)forn,chunkinenumerate(self._shelve_chunks):fp=cStringIO.StringIO()chunk.pretty(fp)markedup=markup(fp)isheader=isinstance(chunk,hgshelve.header)self.diff_model.append([False,markedup,notisheader,isheader,n])finally:difftext.close()self._hg_call_wrapper('Diff',dohgdiff)def_has_shelve_file(self):returnos.path.exists(self.repo.join('shelve'))def_activate_shelve_buttons(self,status):ifstatus:self.shelve_btn.set_sensitive(True)self.unshelve_btn.set_sensitive(self._has_shelve_file())else:self.shelve_btn.set_sensitive(False)self.unshelve_btn.set_sensitive(False)def_showdiff_toggled(self,togglebutton,data=None):# prevent movement events while setting positionself._diffpane.handler_block(self._diffpane_moved_id)iftogglebutton.get_active():self._tree_selection_changed(self.tree.get_selection(),True)self._diffpane.set_position(self._setting_lastpos)else:self._setting_lastpos=self._diffpane.get_position()self._diffpane.set_position(64000)#self.diff_text.set_buffer(gtk.TextBuffer())self._activate_shelve_buttons(togglebutton.get_active())self._diffpane.handler_unblock(self._diffpane_moved_id)returnTruedef_diffpane_moved(self,paned,data=None):# prevent toggle events while setting toolbar stateself.showdiff_toggle.handler_block(self._showdiff_toggled_id)ifself.diffbottom:sizemax=self._diffpane.get_allocation().heightelse:sizemax=self._diffpane.get_allocation().widthifself.showdiff_toggle.get_active():ifpaned.get_position()>=sizemax-55:self.showdiff_toggle.set_active(False)#self.diff_text.set_buffer(gtk.TextBuffer())elifpaned.get_position()<sizemax-55:self.showdiff_toggle.set_active(True)self._tree_selection_changed(self.tree.get_selection(),True)self._activate_shelve_buttons(self.showdiff_toggle.get_active())self.showdiff_toggle.handler_unblock(self._showdiff_toggled_id)returnFalsedef_refresh_clicked(self,toolbutton,data=None):self.reload_status()returnTruedef_revert_clicked(self,toolbutton,data=None):revert_list=self._relevant_files('MAR!')iflen(revert_list)>0:self._hg_revert(revert_list)else:Prompt('Nothing Reverted','No revertable files selected',self).run()returnTruedef_revert_file(self,stat,file):self._hg_revert([file])returnTruedef_log_file(self,stat,file):fromgtoolsimportcmdtablefromhistoryimportGLog# Might want to include 'rev' here... trying withoutstatopts=self.merge_opts(cmdtable['glog|ghistory'][1],('include','exclude','git'))dialog=GLog(self.ui,self.repo,self.cwd,[file],statopts,False)dialog.display()returnTruedef_hg_revert(self,files):wfiles=[self.repo.wjoin(x)forxinfiles]ifself.count_revs()>1:Prompt('Nothing Reverted','Revert is not enabled when multiple revisions are specified.',self).run()return# Create new opts, so nothing unintented gets through.# commands.table revert key changed after 0.9.5, in change d4ec6d61b3eekey='^revert'incommands.tableand'^revert'or'revert'revertopts=self.merge_opts(commands.table[key][1],('include','exclude','rev'))defdohgrevert():commands.revert(self.ui,self.repo,*wfiles,**revertopts)# TODO: Ask which revision when multiple parents (currently just shows abort message)# TODO: Don't need to prompt when reverting added or removed filesifself.count_revs()==1:# rev options needs extra tweaking since is not an array for revert commandrevertopts['rev']=revertopts['rev'][0]dialog=Confirm('Revert',files,self,'Revert files to revision '+revertopts['rev']+'?')else:# rev options needs extra tweaking since it must be an empty string when unspecified for revert commandrevertopts['rev']=''dialog=Confirm('Revert',files,self)ifdialog.run()==gtk.RESPONSE_YES:success,outtext=self._hg_call_wrapper('Revert',dohgrevert)ifsuccess:shell_notify(wfiles)self.reload_status()def_add_clicked(self,toolbutton,data=None):add_list=self._relevant_files('?I')iflen(add_list)>0:self._hg_add(add_list)else:Prompt('Nothing Added','No addable files selected',self).run()returnTruedef_add_file(self,stat,file):self._hg_add([file])returnTruedef_hg_add(self,files):wfiles=[self.repo.wjoin(x)forxinfiles]# Create new opts, so nothing unintented gets throughaddopts=self.merge_opts(commands.table['^add'][1],('include','exclude'))defdohgadd():commands.add(self.ui,self.repo,*wfiles,**addopts)success,outtext=self._hg_call_wrapper('Add',dohgadd)ifsuccess:shell_notify(wfiles)self.reload_status()def_shelve_selected(self):# get list of hunks that have not been rejectedhlist=[x[DM_CHUNK_ID]forxinself.diff_modelifnotx[DM_REJECTED]]ifnothlist:Prompt('Shelve','Please select diff chunks to shelve',self).run()returndoforce=Falsedoappend=Falseifself._has_shelve_file():fromgtklibimportMessageDialogdialog=MessageDialog(flags=gtk.DIALOG_MODAL)dialog.set_title('Shelve')dialog.set_markup('<b>Shelve file exists!</b>')dialog.add_buttons('Overwrite',1,'Append',2,'Cancel',-1)dialog.set_transient_for(self)rval=dialog.run()dialog.destroy()ifrval==-1:returnifrval==1:doforce=Trueifrval==2:doappend=True# capture the selected hunks to shelvefc=[]sc=[]forn,cinenumerate(self._shelve_chunks):ifisinstance(c,hgshelve.header):iflen(fc)>1or(len(fc)==1andfc[0].binary()):sc+=fcfc=[c]elifninhlist:fc.append(c)iflen(fc)>1or(len(fc)==1andfc[0].binary()):sc+=fcdeffilter_patch(ui,chunks):returnsc# shelve them!self.ui.interactive=True# hgshelve only works 'interactively'opts={'addremove':None,'include':[],'force':doforce,'append':doappend,'exclude':[]}hgshelve.filterpatch=filter_patchhgshelve.shelve(self.ui,self.repo,**opts)self.reload_status()def_unshelve(self):opts={'addremove':None,'include':[],'force':None,'append':None,'exclude':[],'inspect':None}try:hgshelve.unshelve(self.ui,self.repo,**opts)self.reload_status()except:passdef_shelve_clicked(self,toolbutton,data=None):self._shelve_selected()self._activate_shelve_buttons(True)def_unshelve_clicked(self,toolbutton,data=None):self._unshelve()self._activate_shelve_buttons(True)def_remove_clicked(self,toolbutton,data=None):remove_list=self._relevant_files('C')delete_list=self._relevant_files('?I')iflen(remove_list)>0:self._hg_remove(remove_list)iflen(delete_list)>0:self._delete_files(delete_list)ifnotremove_listandnotdelete_list:Prompt('Nothing Removed','No removable files selected',self).run()returnTruedef_move_clicked(self,toolbutton,data=None):move_list=self._relevant_files('C')ifmove_list:# get destination directory to files intodialog=gtk.FileChooserDialog(title="Move files to diretory...",parent=self,action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))dialog.set_default_response(gtk.RESPONSE_OK)dialog.set_current_folder(self.repo.root)response=dialog.run()destdir=dialog.get_filename()dialog.destroy()ifresponse!=gtk.RESPONSE_OK:returnTrue# verify directorydestroot=rootpath(destdir)ifdestroot!=self.repo.root:Prompt('Nothing Moved',"Can't move outside repo!",self).run()returnTrue# move the files to dest directorymove_list.append(destdir)self._hg_move(move_list)else:Prompt('Nothing Moved','No movable files selected\n\n''Note: only clean files can be moved.',self).run()returnTruedef_delete_file(self,stat,file):self._delete_files([file])def_delete_files(self,files):dialog=Confirm('Delete unrevisioned',files,self)ifdialog.run()==gtk.RESPONSE_YES:errors=''forfileinfiles:try:os.unlink(self.repo.wjoin(file))exceptException,inst:errors+=str(inst)+'\n\n'iferrors:errors=errors.replace('\\\\','\\')iflen(errors)>500:errors=errors[:errors.find('\n',500)]+'\n...'Prompt('Delete Errors',errors,self).run()self.reload_status()returnTruedef_ignore_file(self,stat,file):ignore=open(self.repo.wjoin('.hgignore'),'a')try:try:ignore.write('glob:'+util.pconvert(file)+'\n')exceptIOError:Prompt('Ignore Failed','Could not update .hgignore',self).run()finally:ignore.close()self.reload_status()returnTruedef_mark_resolved(self,stat,file):ms=merge_.mergestate(self.repo)ms.mark(util.pconvert(file),"r")self.reload_status()def_unmark_resolved(self,stat,file):ms=merge_.mergestate(self.repo)ms.mark(util.pconvert(file),"u")self.reload_status()def_do_resolve(self,stat,file):ms=merge_.mergestate(self.repo)wctx=self.repo[None]mctx=wctx.parents()[-1]ms.resolve(util.pconvert(file),wctx,mctx)self.reload_status()def_sel_clicked(self,state):self._select_files(state)returnTruedef_select_files(self,state,ctype=None):forentryinself.model:ifctypeandnotentry[1]inctype:continueentry[0]=stateself._update_check_count()def_relevant_files(self,stats):return[item[3]foriteminself.modelifitem[0]anditem[1]instats]def_context_menu_act(self,menuitem,handler):selection=self.tree.get_selection()assert(selection.count_selected_rows()==1)list,paths=selection.get_selected_rows()path=paths[0]handler(list[path][1],list[path][3])returnTruedef_tree_button_press(self,widget,event):# Set the flag to ignore the next activation when the shift/control keys are# pressed. This avoids activations with multiple rows selected.ifevent.type==gtk.gdk._2BUTTON_PRESSand \
(event.state&(gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK)):self._ignore_next_act=Trueelse:self._ignore_next_act=FalsereturnFalsedef_tree_button_release(self,widget,event):ifevent.button==3andnot(event.state&(gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK)):self._tree_popup_menu(widget,event.button,event.time)returnFalsedef_get_file_context_menu(self,rowdata):st=rowdata[1]ms=rowdata[4]ifms:menu=self._menus['M'+ms]else:menu=self._menus[st]returnmenudef_tree_popup_menu(self,widget,button=0,time=0):selection=self.tree.get_selection()ifselection.count_selected_rows()!=1:returnFalselist,paths=selection.get_selected_rows()menu=self._get_file_context_menu(list[paths[0]])menu.popup(None,None,None,button,time)returnTruedef_tree_key_press(self,tree,event):ifevent.keyval==32:deftoggler(list,path,iter):list[path][0]=notlist[path][0]selection=self.tree.get_selection()selection.selected_foreach(toggler)returnTruereturnFalsedef_tree_row_act(self,tree,path,column):"""Default action is the first entry in the context menu """# Ignore activations (like double click) on the first column,# and ignore all actions if the flag is setifcolumn.get_sort_column_id()==0orself._ignore_next_act:self._ignore_next_act=FalsereturnTrueselection=self.tree.get_selection()ifselection.count_selected_rows()!=1:returnFalselist,paths=selection.get_selected_rows()menu=self._get_file_context_menu(list[paths[0]])menu.get_children()[0].activate()returnTruedefrun(root='',cwd='',files=[],**opts):u=ui.ui()u.updateopts(debug=False,traceback=False,quiet=True)repo=hg.repository(u,path=root)cmdoptions={'all':False,'clean':False,'ignored':False,'modified':True,'added':True,'removed':True,'deleted':True,'unknown':False,'rev':[],'exclude':[],'include':[],'debug':True,'verbose':True,'git':False}dialog=GStatus(u,repo,cwd,files,cmdoptions,True)gtk.gdk.threads_init()gtk.gdk.threads_enter()dialog.display()gtk.main()gtk.gdk.threads_leave()if__name__=="__main__":importsysopts={}opts['root']=len(sys.argv)>1andsys.argv[1]or''run(**opts)
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.