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.
* hg cat only works on revisions that include modifications to the file, so I expanded the check for annotation to include this feature * Checking for revlog.LookupError() also catches revisions where the file has been deleted (removed from the manifest but not changed otherwise) * This was added as the second item in the context menu to avoid making it the default action when a row is activated in the file list.
## changeset.py - Changeset dialog for TortoiseHg## Copyright 2008 Steve Borho <steve@borho.org>#importosimportsubprocessimportsysimporttimeimportpygtkpygtk.require('2.0')importgtkimportgobjectimportpangoimportStringIOfrom mercurial.i18n import _
from mercurial.node import *
-from mercurial import cmdutil, util, ui, hg, commands, patch
+from mercurial import cmdutil, util, ui, hg, commands, patch, revlogfrom gdialog import *
from hgcmd import CmdDialog
classChangeSet(GDialog):"""GTK+ based dialog for displaying repository logs """defget_title(self):title=os.path.basename(self.repo.root)+' changeset 'title+=self.opts['rev'][0]returntitledefget_icon(self):return'menushowchanged.ico'defget_tbbuttons(self):self.parent_toggle=gtk.ToggleToolButton(gtk.STOCK_UNDO)self.parent_toggle.set_use_underline(True)self.parent_toggle.set_label('_other parent')self.parent_toggle.set_tooltip(self.tooltips,'diff other parent')self.parent_toggle.set_sensitive(False)self.parent_toggle.set_active(False)self.parent_toggle.connect('toggled',self._parent_toggled)return[self.parent_toggle]def_parent_toggled(self,button):self.load_details(self.currev)defprepare_display(self):self.currow=Noneself.graphview=Noneself.glog_parent=Nonenode0,node1=cmdutil.revpair(self.repo,self.opts.get('rev'))self.load_details(self.repo.changelog.rev(node0))defsave_settings(self):settings=GDialog.save_settings(self)settings['changeset']=self._hpaned.get_position()returnsettingsdefload_settings(self,settings):GDialog.load_settings(self,settings)ifsettingsand'changeset'insettings:self._setting_hpos=settings['changeset']else:self._setting_hpos=-1defload_details(self,rev):'''Load selected changeset details into buffer and filelist'''self.currev=revself._buffer.set_text('')self._filelist.clear()parents=[xforxinself.repo.changelog.parentrevs(rev) \
ifx!=nullrev]self.parents=parentstitle=self.get_title()iflen(parents)==2:self.parent_toggle.set_sensitive(True)ifself.parent_toggle.get_active():title+=':'+str(self.parents[1])else:title+=':'+str(self.parents[0])else:self.parent_toggle.set_sensitive(False)ifself.parent_toggle.get_active():# Parent button must be pushed out, but this# will cause load_details to be called again# so we exit out to prevent recursion.self.parent_toggle.set_active(False)returnctx=self.repo.changectx(rev)ifnotctx:self._last_rev=NonereturnFalseself.set_title(title)self.textview.freeze_child_notify()try:self._fill_buffer(self._buffer,rev,ctx,self._filelist)finally:self.textview.thaw_child_notify()def_fill_buffer(self,buf,rev,ctx,filelist):deftitle_line(title,text,tag):pad=' '*(12-len(title))buf.insert_with_tags_by_name(eob,title+pad+text,tag)buf.insert(eob,"\n")# TODO: Add toggle for gmtime/localtimeeob=buf.get_end_iter()date=time.strftime("%Y-%m-%d %H:%M:%S",time.gmtime(ctx.date()[0]))self.clipboard.set_text(short(ctx.node()))change=str(rev)+':'+short(ctx.node())tags=' '.join(ctx.tags())parents=self.parentstitle_line('changeset:',change,'changeset')ifctx.branch()!='default':title_line('branch:',ctx.branch(),'greybg')title_line('user/date:',ctx.user()+'\t'+date,'changeset')forpinparents:pctx=self.repo.changectx(p)summary=pctx.description().splitlines()[0]change=str(p)+':'+short(self.repo.changelog.node(p))title='parent:'title+=' '*(12-len(title))buf.insert_with_tags_by_name(eob,title,'parent')buf.insert_with_tags_by_name(eob,change,'link')buf.insert_with_tags_by_name(eob,' '+summary,'parent')buf.insert(eob,"\n")forninself.repo.changelog.children(ctx.node()):cctx=self.repo.changectx(n)summary=cctx.description().splitlines()[0]childrev=self.repo.changelog.rev(n)change=str(childrev)+':'+short(n)title='child:'title+=' '*(12-len(title))buf.insert_with_tags_by_name(eob,title,'parent')buf.insert_with_tags_by_name(eob,change,'link')buf.insert_with_tags_by_name(eob,' '+summary,'parent')buf.insert(eob,"\n")forninself.repo.changelog.children(ctx.node()):childrev=self.repo.changelog.rev(n)iftags:title_line('tags:',tags,'tag')log=util.fromlocal(ctx.description())buf.insert(eob,'\n'+log+'\n\n')offset=eob.get_offset()ifself.parent_toggle.get_active():parent=self.repo.changelog.node(parents[1])elifparents:parent=self.repo.changelog.node(parents[0])else:parent=nullidout=StringIO.StringIO()patch.diff(self.repo,node1=parent,node2=ctx.node(),files=ctx.files(),fp=out)txt=out.getvalue()lines=unicode(txt,'latin-1','replace').splitlines()fileoffs,tags,lines,statmax=self.prepare_diff(lines,offset)forlinlines:buf.insert(eob,l)# inserts the tagsforname,p0,p1intags:i0=buf.get_iter_at_offset(p0)i1=buf.get_iter_at_offset(p1)txt=buf.get_text(i0,i1)buf.apply_tag_by_name(name,i0,i1)buf.create_mark('begmark',buf.get_start_iter())filelist.append(('[Description]','begmark',False,()))# inserts the marksforf,mark,offset,statsinfileoffs:pos=buf.get_iter_at_offset(offset)buf.create_mark(mark,pos)filelist.append((f,mark,True,(stats[0],stats[1],statmax)))sob,eob=buf.get_bounds()buf.apply_tag_by_name("mono",sob,eob)defprepare_diff(self,difflines,offset):'''Borrowed from hgview; parses changeset diffs'''DIFFHDR="=== %s ===\n"idx=0outlines=[]tags=[]filespos=[]defaddtag(name,offset,length):iftagsandtags[-1][0]==nameandtags[-1][2]==offset:tags[-1][2]+=lengthelse:tags.append([name,offset,offset+length])stats=[0,0]statmax=0fori,linenumerate(difflines):ifl.startswith("diff"):f=l.split()[-1]txt=DIFFHDR%faddtag("greybg",offset,len(txt))outlines.append(txt)markname="file%d"%idxidx+=1statmax=max(statmax,stats[0]+stats[1])stats=[0,0]filespos.append((f,markname,offset,stats))offset+=len(txt)continueelifl.startswith("+++"):continueelifl.startswith("---"):continueelifl.startswith("+"):tag="green"stats[0]+=1elifl.startswith("-"):stats[1]+=1tag="red"elifl.startswith("@@"):tag="blue"else:tag="black"l=l+"\n"length=len(l)addtag(tag,offset,length)outlines.append(l)offset+=lengthstatmax=max(statmax,stats[0]+stats[1])returnfilespos,tags,outlines,statmaxdeflink_event(self,tag,widget,event,iter):ifevent.type!=gtk.gdk.BUTTON_RELEASE:returntext=self.get_link_text(tag,widget,iter)ifnottext:returnlinkrev=long(text.split(':')[0])ifself.graphview:self.graphview.set_revision_id(linkrev)self.graphview.scroll_to_revision(linkrev)else:self.load_details(linkrev)defget_link_text(self,tag,widget,iter):"""handle clicking on a link in a textview"""text_buffer=widget.get_buffer()beg=iter.copy()whilenotbeg.begins_tag(tag):beg.backward_char()end=iter.copy()whilenotend.ends_tag(tag):end.forward_char()text=text_buffer.get_text(beg,end)returntextdeffile_context_menu(self):defcreate_menu(label,callback):menuitem=gtk.MenuItem(label,True)menuitem.connect('activate',callback)menuitem.set_border_width(1)returnmenuitem _menu = gtk.Menu()
_menu.append(create_menu('_view at revision', self._view_file_rev))
+ self._save_menu = create_menu('_save at revision', self._save_file_rev)+ _menu.append(self._save_menu) _menu.append(create_menu('_file history', self._file_history))
self._ann_menu = create_menu('_annotate file', self._ann_file)
_menu.append(self._ann_menu)
_menu.append(create_menu('_revert file contents',self._revert_file))self._file_diff_to_mark_menu=create_menu('_diff file to mark',self._diff_file_to_mark)self._file_diff_from_mark_menu=create_menu('diff file _from mark',self._diff_file_from_mark)_menu.append(self._file_diff_to_mark_menu)_menu.append(self._file_diff_from_mark_menu)_menu.show_all()return_menudefget_body(self):sel=(os.name=='nt')and'CLIPBOARD'or'PRIMARY'self.clipboard=gtk.Clipboard(selection=sel)self._filemenu=self.file_context_menu()details_frame=gtk.Frame()details_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)scroller=gtk.ScrolledWindow()scroller.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)details_frame.add(scroller)details_text=gtk.TextView()details_text.set_wrap_mode(gtk.WRAP_NONE)details_text.set_editable(False)details_text.modify_font(pango.FontDescription(self.fontcomment))scroller.add(details_text)self._buffer=gtk.TextBuffer()self.setup_tags()details_text.set_buffer(self._buffer)self.textview=details_textfilelist_tree=gtk.TreeView()filesel=filelist_tree.get_selection()filesel.connect("changed",self._filelist_rowchanged)filelist_tree.connect('button-release-event',self._file_button_release)filelist_tree.connect('popup-menu',self._file_popup_menu)filelist_tree.connect('row-activated',self._file_row_act)self._filelist=gtk.ListStore(gobject.TYPE_STRING,# filenamegobject.TYPE_PYOBJECT,# markgobject.TYPE_PYOBJECT,# give cmenugobject.TYPE_PYOBJECT# diffstats)filelist_tree.set_model(self._filelist)column=gtk.TreeViewColumn('Files',gtk.CellRendererText(),text=0)filelist_tree.append_column(column)list_frame=gtk.Frame()list_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)scroller=gtk.ScrolledWindow()scroller.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)scroller.add(filelist_tree)list_frame.add(scroller)self._hpaned=gtk.HPaned()self._hpaned.pack1(details_frame,True,True)self._hpaned.pack2(list_frame,True,True)self._hpaned.set_position(self._setting_hpos)returnself._hpaneddefsetup_tags(self):"""Creates the tags to be used inside the TextView"""defmake_texttag(name,**kwargs):"""Helper function generating a TextTag"""tag=gtk.TextTag(name)forkey,valueinkwargs.iteritems():key=key.replace("_","-")try:tag.set_property(key,value)exceptTypeError:print"Warning the property %s is unsupported in"%keyprint"this version of pygtk"returntagtag_table=self._buffer.get_tag_table()tag_table.add(make_texttag('changeset',foreground='#000090',paragraph_background='#F0F0F0'))tag_table.add(make_texttag('date',foreground='#000090',paragraph_background='#F0F0F0'))tag_table.add(make_texttag('tag',foreground='#000090',paragraph_background='#F0F0F0'))tag_table.add(make_texttag('files',foreground='#5C5C5C',paragraph_background='#F0F0F0'))tag_table.add(make_texttag('parent',foreground='#000090',paragraph_background='#F0F0F0'))tag_table.add(make_texttag("mono",family="Monospace"))tag_table.add(make_texttag("blue",foreground='blue'))tag_table.add(make_texttag("red",foreground='red'))tag_table.add(make_texttag("green",foreground='darkgreen'))tag_table.add(make_texttag("black",foreground='black'))tag_table.add(make_texttag("greybg",paragraph_background='grey',weight=pango.WEIGHT_BOLD))tag_table.add(make_texttag("yellowbg",background='yellow'))link_tag=make_texttag("link",foreground="blue",underline=pango.UNDERLINE_SINGLE)link_tag.connect("event",self.link_event)tag_table.add(link_tag)def_filelist_rowchanged(self,sel):model,iter=sel.get_selected()ifnotiter:return# scroll to file in details windowmark=self._buffer.get_mark(model[iter][1])self.textview.scroll_to_mark(mark,0.0,True,0.0,0.0)ifmodel[iter][2]:self.curfile=model[iter][0]else:self.curfile=Nonedef_file_button_release(self,widget,event):ifevent.button==3andnot(event.state&(gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK)):self._file_popup_menu(widget,event.button,event.time)returnFalsedef_file_popup_menu(self,treeview,button=0,time=0):ifself.curfileisNone:returnifself.graphview:is_mark=self.graphview.get_mark_rev()isnotNoneelse:is_mark=Falseself._file_diff_to_mark_menu.set_sensitive(is_mark)self._file_diff_from_mark_menu.set_sensitive(is_mark)self._filemenu.popup(None,None,None,button,time)# If the filelog entry this changeset references does not link# back to this changeset, it means this changeset did not# actually change the contents of this file, and thus the file # cannot be annotated at this revision (since this changeset
# does not appear in the filelog)
ctx = self.repo.changectx(self.currev)
- fctx = ctx.filectx(self.curfile)
-can_annotate = fctx.filelog().linkrev(fctx.filenode()) == ctx.rev()
- self._ann_menu.set_sensitive(can_annotate)
+ try:+ fctx = ctx.filectx(self.curfile)
+ has_filelog = fctx.filelog().linkrev(fctx.filenode()) == ctx.rev()
+except revlog.LookupError:+ has_filelog = False+self._ann_menu.set_sensitive(has_filelog)
+ self._save_menu.set_sensitive(has_filelog) return True
def _file_row_act(self, tree, path, column) :
"""Default action is the first entry in the context menu """ self._filemenu.get_children()[0].activate()
return True
+ def _save_file_rev(self, menuitem):+ file, ext = os.path.splitext(os.path.basename(self.curfile))+ filename = "%s@%d%s" % (file, self.currev, ext)+ fd = NativeSaveFileDialogWrapper(Title = "Save file to",+ InitialDir=self.cwd,+ FileName=filename)+ result = fd.run()+ if result:+ import Queue+ import hglib+ q = Queue.Queue()+ cpath = util.canonpath(self.repo.root, self.cwd, self.curfile)+ hglib.hgcmd_toq(self.repo.root, q, 'cat', '--rev',+ str(self.currev), '--output', result, cpath)+ def _view_file_rev(self, menuitem):
'''User selected view file revision from the file list context menu'''
- # TODO rev = self.currev
parents = self.parents
if len(parents) == 0:
parent=rev-1else:parent=parents[0]pair='%u:%u'%(parent,rev)self._node1,self._node2=cmdutil.revpair(self.repo,[pair])self._view_file('M',self.curfile,force_left=False)def_diff_file_to_mark(self,menuitem):'''User selected diff to mark from the file list context menu'''fromstatusimportGStatusfromgtoolsimportcmdtablerev0=self.graphview.get_mark_rev()rev1=self.currevstatopts=self.merge_opts(cmdtable['gstatus|gst'][1],('include','exclude','git'))statopts['rev']=['%u:%u'%(rev1,rev0)]statopts['modified']=Truestatopts['added']=Truestatopts['removed']=Truedialog=GStatus(self.ui,self.repo,self.cwd,[self.curfile],statopts,False)dialog.display()returnTruedef_diff_file_from_mark(self,menuitem):'''User selected diff from mark from the file list context menu'''fromstatusimportGStatusfromgtoolsimportcmdtablerev0=self.graphview.get_mark_rev()rev1=self.currevstatopts=self.merge_opts(cmdtable['gstatus|gst'][1],('include','exclude','git'))statopts['rev']=['%u:%u'%(rev0,rev1)]statopts['modified']=Truestatopts['added']=Truestatopts['removed']=Truedialog=GStatus(self.ui,self.repo,self.cwd,[self.curfile],statopts,False)dialog.display()def_ann_file(self,menuitem):'''User selected diff from mark from the file list context menu'''fromdatamineimportDataMineDialogrev=self.currevdialog=DataMineDialog(self.ui,self.repo,self.cwd,[],{},False)dialog.display()dialog.add_annotate_page(self.curfile,str(rev))def_file_history(self,menuitem):'''User selected file history from file list context menu'''ifself.glog_parent:# If this changeset browser is embedded in glog, send# send this event to the main appopts={'filehist':self.curfile}self.glog_parent.custombutton.set_active(True)self.glog_parent.graphview.refresh(True,None,opts)else:# Else launch our own GLog instancefromhistoryimportGLogdialog=GLog(self.ui,self.repo,self.cwd,[self.repo.root],{},False)dialog.open_with_file(self.curfile)dialog.display()def_revert_file(self,menuitem):'''User selected file revert from the file list context menu'''rev=self.currevdialog=Confirm('revert file to old revision',[],self,'Revert %s to contents at revision %d?'%(self.curfile,rev))ifdialog.run()==gtk.RESPONSE_NO:returncmdline=['hg','revert','--verbose','--rev',str(rev),self.curfile]self.restore_cwd()dlg=CmdDialog(cmdline)dlg.run()dlg.hide()shell_notify([self.curfile])defrun(root='',cwd='',files=[],**opts):u=ui.ui()u.updateopts(debug=False,traceback=False)repo=hg.repository(u,path=root)dialog=ChangeSet(u,repo,cwd,files,opts,True)dialog.display()gtk.gdk.threads_init()gtk.gdk.threads_enter()gtk.main()gtk.gdk.threads_leave()if__name__=="__main__":importsysopts={}opts['root']=len(sys.argv)>1andsys.argv[1]oros.getcwd()opts['rev']=['750']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.