class GLog(GDialog): """GTK+ based dialog for displaying repository logs """ def get_title(self): return os.path.basename(self.repo.root) + ' log' def get_icon(self): return 'menulog.ico' def parse_opts(self): # Disable quiet to get full log info self.ui.quiet = False def get_tbbuttons(self): return [ self.make_toolbutton(gtk.STOCK_REFRESH, 'Re_fresh', self._refresh_clicked, tip='Reload revision history'), gtk.SeparatorToolItem(), self.make_toolbutton(gtk.STOCK_INDEX, '_Filter', self._filter_clicked, menu=self._filter_menu(), tip='Filter revisions for display'), gtk.SeparatorToolItem(), self.make_toolbutton(gtk.STOCK_FIND, '_DataMine', self._datamine_clicked, tip='Search Repository History'), gtk.SeparatorToolItem() ] + self.changeview.get_tbbuttons() def toggle_view_column(self, button, property): bool = button.get_active() self.graphview.set_property(property, bool) def _more_clicked(self, button): self.graphview.next_revision_batch() def _load_all_clicked(self, button): self.graphview.load_all_revisions() self.nextbutton.set_sensitive(False) self.allbutton.set_sensitive(False) def revisions_loaded(self, graphview): '''Treeview reports log generator has exited''' if not self.graphview.graphdata: self.changeview._buffer.set_text('') self.changeview._filelist.clear() self._last_rev = None self.nextbutton.set_sensitive(False) self.allbutton.set_sensitive(False) def _datamine_clicked(self, toolbutton, data=None): from datamine import DataMineDialog dialog = DataMineDialog(self.ui, self.repo, self.cwd, [], {}, False) dialog.display() dialog.add_search_page() def _filter_clicked(self, toolbutton, data=None): if self._filter_dialog: self._filter_dialog.show() self._filter_dialog.present() else: self._show_filter_dialog() def _show_filter_dialog(self): '''Launch a modeless filter dialog''' def do_reload(opts): self.custombutton.set_active(True) self.reload_log(opts) def close_filter_dialog(dialog, response_id): dialog.hide() revs = [] if self.currow is not None: revs.append(self.currow[treemodel.REVID]) if self.graphview.get_mark_rev() is not None: revs.append(self.graphview.get_mark_rev()) dlg = FilterDialog(self.repo.root, revs, self.pats, filterfunc=do_reload) dlg.connect('response', close_filter_dialog) dlg.set_modal(False) dlg.show() self._filter_dialog = dlg def _filter_selected(self, widget, data=None): if widget.get_active(): self._filter = data self.reload_log() def _view_menu(self): menu = gtk.Menu() button = gtk.CheckMenuItem("Show Ids") button.connect("toggled", self.toggle_view_column, 'rev-column-visible') button.set_active(True) button.set_draw_as_radio(True) menu.append(button) button = gtk.CheckMenuItem("Show Tags") button.connect("toggled", self.toggle_view_column, 'tags-column-visible') button.set_active(True) button.set_draw_as_radio(True) menu.append(button) button = gtk.CheckMenuItem("Show Date") button.connect("toggled", self.toggle_view_column, 'date-column-visible') button.set_active(True) button.set_draw_as_radio(True) menu.append(button) menu.show_all() return menu def _filter_menu(self): menu = gtk.Menu() button = gtk.RadioMenuItem(None, "Show All Revisions") button.set_active(True) button.connect("toggled", self._filter_selected, 'all') menu.append(button) button = gtk.RadioMenuItem(button, "Show Tagged Revisions") button.connect("toggled", self._filter_selected, 'tagged') menu.append(button) button = gtk.RadioMenuItem(button, "Show Parent Revisions") button.connect("toggled", self._filter_selected, 'parents') menu.append(button) button = gtk.RadioMenuItem(button, "Show Head Revisions") button.connect("toggled", self._filter_selected, 'heads') menu.append(button) button = gtk.RadioMenuItem(button, "Show Only Merge Revisions") button.connect("toggled", self._filter_selected, 'only_merges') menu.append(button) button = gtk.RadioMenuItem(button, "Show Non-Merge Revisions") button.connect("toggled", self._filter_selected, 'no_merges') menu.append(button) self.custombutton = gtk.RadioMenuItem(button, "Custom Filter") self.custombutton.set_sensitive(False) menu.append(self.custombutton) menu.show_all() return menu def open_with_file(self, file): '''Call this before display() to open with file history''' self.opts['filehist'] = file def prepare_display(self): '''Called at end of display() method''' self._last_rev = None self._filter = "all" self.currow = None self.curfile = None self.opts['rev'] = [] # This option is dangerous - used directly by hg self.opts['revs'] = None os.chdir(self.repo.root) # paths relative to repo root do not work otherwise if 'filehist' in self.opts: self.custombutton.set_active(True) self.graphview.refresh(True, None, self.opts) del self.opts['filehist'] elif 'revrange' in self.opts: self.custombutton.set_active(True) self.graphview.refresh(True, None, self.opts) elif self.pats == [self.repo.root] or self.pats == ['']: self.pats = [] self.reload_log() elif self.pats: self.custombutton.set_active(True) self.graphview.refresh(False, self.pats, self.opts) else: self.reload_log() def save_settings(self): settings = GDialog.save_settings(self) settings['glog'] = (self._vpaned.get_position(), self._hpaned.get_position()) return settings def load_settings(self, settings): '''Called at beginning of display() method''' limit_opt = self.repo.ui.config('tortoisehg', 'graphlimit', '500') if limit_opt: try: limit = int(limit_opt) except ValueError: limit = 0 if limit <= 0: limit = None else: limit = None # Allocate TreeView instance to use internally self.limit = limit self.stbar = gtklib.StatusBar() self.graphview = TreeView(self.repo, limit, self.stbar) # Allocate ChangeSet instance to use internally self.changeview = ChangeSet(self.ui, self.repo, self.cwd, [], self.opts, False, self.stbar) self.changeview.display(False) self.changeview.glog_parent = self GDialog.load_settings(self, settings) if settings: set = settings['glog'] if type(set) == int: self._setting_vpos = set self._setting_hpos = -1 else: (self._setting_vpos, self._setting_hpos) = set else: self._setting_vpos = -1 self._setting_hpos = -1 def reload_log(self, filteropts={}): """Send refresh event to treeview object""" os.chdir(self.repo.root) # paths relative to repo root do not work otherwise self.nextbutton.set_sensitive(True) self.allbutton.set_sensitive(True) self.opts['rev'] = [] self.opts['revs'] = None self.opts['no_merges'] = False self.opts['only_merges'] = False self.opts['revrange'] = filteropts.get('revrange', None) self.opts['date'] = filteropts.get('date', None) self.opts['keyword'] = filteropts.get('keyword', []) revs = [] if filteropts: branch = filteropts.get('branch', None) if 'revrange' in filteropts or 'branch' in filteropts: self.graphview.refresh(True, branch, self.opts) else: filter = filteropts.get('pats', []) self.graphview.refresh(False, filter, self.opts) elif self._filter == "all": self.graphview.refresh(True, None, self.opts) elif self._filter == "only_merges": self.opts['only_merges'] = True self.graphview.refresh(False, [], self.opts) elif self._filter == "no_merges": self.opts['no_merges'] = True self.graphview.refresh(False, [], self.opts) elif self._filter == "tagged": tagged = [] for t, r in self.repo.tagslist(): hr = hex(r) if hr not in tagged: tagged.insert(0, hr) self.opts['revs'] = tagged self.graphview.refresh(False, [], self.opts) elif self._filter == "parents": repo_parents = [x.rev() for x in self.repo.workingctx().parents()] self.opts['revs'] = [str(x) for x in repo_parents] self.graphview.refresh(False, [], self.opts) elif self._filter == "heads": heads = [self.repo.changelog.rev(x) for x in self.repo.heads()] self.opts['revs'] = [str(x) for x in heads] self.graphview.refresh(False, [], self.opts) def tree_context_menu(self): _menu = gtk.Menu() _menu.append(create_menu('di_splay', self._show_status)) _menu.append(create_menu('_checkout', self._checkout)) self._cmenu_merge = create_menu('_merge with', self._merge) _menu.append(self._cmenu_merge) _menu.append(create_menu('_export patch', self._export_patch)) _menu.append(create_menu('e_mail patch', self._email_patch)) _menu.append(create_menu('add/remove _tag', self._add_tag)) _menu.append(create_menu('backout revision', self._backout_rev)) # need mq extension for strip command extensions.loadall(self.ui) extensions.load(self.ui, 'mq', None) _menu.append(create_menu('strip revision', self._strip_rev)) _menu.show_all() return _menu def tree_diff_context_menu(self): _menu = gtk.Menu() _menu.append(create_menu('_diff with selected', self._diff_revs)) _menu.append(create_menu('visual diff with selected', self._vdiff_selected)) _menu.show_all() return _menu def get_body(self): self._filter_dialog = None self._menu = self.tree_context_menu() self._menu2 = self.tree_diff_context_menu() self.tree_frame = gtk.Frame() self.tree_frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) # PyGtk 2.6 and below did not automatically register types if gobject.pygtk_version < (2, 8, 0): gobject.type_register(TreeView) self.tree = self.graphview.treeview self.graphview.connect('revision-selected', self.selection_changed) self.graphview.connect('revisions-loaded', self.revisions_loaded) #self.tree.connect('button-release-event', self._tree_button_release) self.tree.connect('button-press-event', self._tree_button_press) #self.tree.connect('popup-menu', self._tree_popup_menu) self.tree.connect('row-activated', self._tree_row_act) #self.tree.modify_font(pango.FontDescription(self.fontlist)) hbox = gtk.HBox() hbox.pack_start(self.graphview, True, True, 0) vbox = gtk.VBox() self.colmenu = gtk.MenuToolButton('') self.colmenu.set_menu(self._view_menu()) # A MenuToolButton has two parts; a Button and a ToggleButton # we want to see the togglebutton, but not the button b = self.colmenu.child.get_children()[0] b.unmap() b.set_sensitive(False) self.nextbutton = gtk.ToolButton(gtk.STOCK_GO_DOWN) self.nextbutton.connect('clicked', self._more_clicked) self.allbutton = gtk.ToolButton(gtk.STOCK_GOTO_BOTTOM) self.allbutton.connect('clicked', self._load_all_clicked) vbox.pack_start(self.colmenu, False, False) vbox.pack_start(gtk.Label(''), True, True) # expanding blank label vbox.pack_start(self.nextbutton, False, False) vbox.pack_start(self.allbutton, False, False) self.nextbutton.set_tooltip(self.tooltips, 'show next %d revisions' % self.limit) self.allbutton.set_tooltip(self.tooltips, 'show all remaining revisions') hbox.pack_start(vbox, False, False, 0) self.tree_frame.add(hbox) self.tree_frame.show_all() # Add ChangeSet instance to bottom half of vpane self.changeview.graphview = self.graphview self._hpaned = self.changeview.get_body() self._vpaned = gtk.VPaned() self._vpaned.pack1(self.tree_frame, True, False) self._vpaned.pack2(self._hpaned) self._vpaned.set_position(self._setting_vpos) self._hpaned.set_position(self._setting_hpos) vbox = gtk.VBox() vbox.pack_start(self._vpaned, True, True) # Append status bar vbox.pack_start(gtk.HSeparator(), False, False) vbox.pack_start(self.stbar, False, False) return vbox def _strip_rev(self, menuitem): rev = self.currow[treemodel.REVID] res = Confirm('Strip Revision(s)', [], self, 'Remove revision %d and all descendants?' % rev).run() if res != gtk.RESPONSE_YES: return from hgcmd import CmdDialog cmdline = ['hg', 'strip', str(rev)] dlg = CmdDialog(cmdline) dlg.show_all() dlg.run() dlg.hide() self.repo.invalidate() self.reload_log() def _backout_rev(self, menuitem): from backout import BackoutDialog rev = self.currow[treemodel.REVID] rev = short(self.repo.changelog.node(rev)) parents = [x.node() for x in self.repo.workingctx().parents()] dialog = BackoutDialog(self.repo.root, rev) dialog.set_transient_for(self) dialog.show_all() dialog.set_notify_func(self.checkout_completed, parents) dialog.present() dialog.set_transient_for(None) def _diff_revs(self, menuitem): from status import GStatus from gtools import cmdtable rev0, rev1 = self._revs statopts = self.merge_opts(cmdtable['gstatus|gst'][1], ('include', 'exclude', 'git')) statopts['rev'] = ['%u:%u' % (rev0, rev1)] statopts['modified'] = True statopts['added'] = True statopts['removed'] = True dialog = GStatus(self.ui, self.repo, self.cwd, [], statopts, False) dialog.display() return True def _vdiff_selected(self, menuitem): rev0, rev1 = self._revs self.opts['rev'] = ["%s:%s" % (rev0, rev1)] self._diff_file(None, '') def _mark_rev(self, menuitem): rev = self.currow[treemodel.REVID] self.graphview.set_mark_rev(rev) def _add_tag(self, menuitem): from tagadd import TagAddDialog rev = self.currow[treemodel.REVID] parents = self.currow[treemodel.PARENTS] # save tag info for detecting new tags added oldtags = self.repo.tagslist() def refresh(*args): self.repo.invalidate() newtags = self.repo.tagslist() if newtags != oldtags: self.reload_log() dialog = TagAddDialog(self.repo.root, rev=str(rev)) dialog.set_transient_for(self) dialog.connect('destroy', refresh) dialog.show_all() dialog.present() dialog.set_transient_for(None) def _show_status(self, menuitem): rev = self.currow[treemodel.REVID] statopts = {'rev' : [str(rev)] } dialog = ChangeSet(self.ui, self.repo, self.cwd, [], statopts, False) dialog.display() def _export_patch(self, menuitem): rev = self.currow[treemodel.REVID] filename = "%s_rev%s.patch" % (os.path.basename(self.repo.root), rev) fd = NativeSaveFileDialogWrapper(Title = "Save patch to", InitialDir=self.repo.root, FileName=filename) result = fd.run() if result: # In case new export args are added in the future, merge the # hg defaults exportOpts= self.merge_opts(commands.table['^export'][1], ()) exportOpts['output'] = result def dohgexport(): commands.export(self.ui,self.repo,str(rev),**exportOpts) success, outtext = self._hg_call_wrapper("Export",dohgexport,False) def _email_patch(self, menuitem): from hgemail import EmailDialog rev = self.currow[treemodel.REVID] dlg = EmailDialog(self.repo.root, ['--rev', str(rev)]) dlg.set_transient_for(self) dlg.show_all() dlg.present() dlg.set_transient_for(None) def _checkout(self, menuitem): rev = self.currow[treemodel.REVID] parents = [x.node() for x in self.repo.workingctx().parents()] dialog = UpdateDialog(self.cwd, rev) dialog.set_transient_for(self) dialog.show_all() dialog.set_notify_func(self.checkout_completed, parents) dialog.present() dialog.set_transient_for(None) def checkout_completed(self, oldparents): newparents = [x.node() for x in self.repo.workingctx().parents()] if not oldparents == newparents: self.reload_log() def _merge(self, menuitem): rev = self.currow[treemodel.REVID] parents = [x.node() for x in self.repo.workingctx().parents()] node = short(self.repo.changelog.node(rev)) dialog = MergeDialog(self.repo.root, self.cwd, node) dialog.set_transient_for(self) dialog.show_all() dialog.set_notify_func(self.merge_completed, parents) dialog.present() dialog.set_transient_for(None) def merge_completed(self, oldparents): newparents = [x.node() for x in self.repo.workingctx().parents()] if not oldparents == newparents: self.reload_log() def selection_changed(self, treeview): self.currow = self.graphview.get_revision() rev = self.currow[treemodel.REVID] if rev != self._last_rev: self._last_rev = rev self.changeview.opts['rev'] = [str(rev)] self.changeview.load_details(rev) return False def _refresh_clicked(self, toolbutton, data=None): self.reload_log() return True def _tree_button_release(self, widget, event) : if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK)): self._tree_popup_menu(widget, event.button, event.time) return False def _tree_button_press(self, widget, event): if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK)): crow = widget.get_path_at_pos(int(event.x), int(event.y))[0] (model, pathlist) = widget.get_selection().get_selected_rows() if pathlist == []: return False srow = pathlist[0] if srow == crow: self._tree_popup_menu(widget, event.button, event.time) else: self._revs = (int(model[srow][treemodel.REVID]), int(model[crow][treemodel.REVID])) self._tree_popup_menu_diff(widget, event.button, event.time) return True return False def _tree_popup_menu(self, treeview, button=0, time=0) : selrev = self.currow[treemodel.REVID] # disable/enable menus as required parents = [self.repo.changelog.rev(x.node()) for x in self.repo.workingctx().parents()] can_merge = selrev not in parents and \ len(self.repo.heads()) > 1 and \ len(parents) < 2 self._cmenu_merge.set_sensitive(can_merge) # display the context menu self._menu.popup(None, None, None, button, time) return True def _tree_popup_menu_diff(self, treeview, button=0, time=0): # display the context menu self._menu2.popup(None, None, None, button, time) return True def _tree_row_act(self, tree, path, column) : """Default action is the first entry in the context menu """ self._menu.get_children()[0].activate() return True
def add_annotate_page(self, path, revid): ''' Add new annotation page to notebook. Start scan of file 'path' revision history, start annotate of supplied revision 'revid'. ''' if revid == '.': ctx = self.repo.workingctx().parents()[0] try: fctx = ctx.filectx(path) except revlog.LookupError: Prompt('File is unrevisioned', 'Unable to annotate ' + path, self).run() return rev = fctx.filelog().linkrev(fctx.filenode()) revid = str(rev) else: rev = long(revid) frame = gtk.Frame() frame.set_border_width(10) vbox = gtk.VBox() # File log revision graph graphview = TreeView(self.repo, 5000, self.stbar) graphview.connect('revisions-loaded', self.revisions_loaded, rev) graphview.refresh(True, None, {'filehist':path, 'filerev':rev}) graphview.set_property('rev-column-visible', True) graphview.set_property('date-column-visible', True) hbox = gtk.HBox() followlabel = gtk.Label('') follow = gtk.Button('Follow') follow.connect('clicked', self.follow_rename) follow.hide() follow.set_sensitive(False) hbox.pack_start(gtk.Label(''), True, True) hbox.pack_start(followlabel, False, False) hbox.pack_start(follow, False, False) # Annotation text tree view treeview = gtk.TreeView() treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) treeview.set_property('fixed-height-mode', True) treeview.set_border_width(0) treeview.connect("cursor-changed", self._ann_selection_changed) treeview.connect('button-release-event', self._ann_button_release) treeview.connect('popup-menu', self._ann_popup_menu) treeview.connect('row-activated', self._ann_row_act) results = gtk.ListStore(str, str, str, str, str, str) treeview.set_model(results) context_menu = self.ann_header_context_menu(treeview) for title, width, col, emode, visible in ( ('Rev', 10, self.COL_REVID, pango.ELLIPSIZE_NONE, True), ('File', 15, self.COL_PATH, pango.ELLIPSIZE_START, False), ('User', 15, self.COL_USER, pango.ELLIPSIZE_END, False), ('Matches', 80, self.COL_TEXT, pango.ELLIPSIZE_END, True)): cell = gtk.CellRendererText() cell.set_property("width-chars", width) cell.set_property("ellipsize", emode) cell.set_property("family", "Monospace") column = gtk.TreeViewColumn(title) column.set_resizable(True) column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(cell.get_size(treeview)[2]) column.pack_start(cell, expand=True) column.add_attribute(cell, "text", col) column.add_attribute(cell, "background", self.COL_COLOR) column.set_visible(visible) treeview.append_column(column) self._add_header_context_menu(column, context_menu) treeview.set_headers_clickable(True) if hasattr(treeview, 'set_tooltip_column'): treeview.set_tooltip_column(self.COL_TOOLTIP) results.path = path results.rev = revid scroller = gtk.ScrolledWindow() scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroller.add(treeview) vpaned = gtk.VPaned() vpaned.pack1(graphview, True, True) vpaned.pack2(scroller, True, True) vbox.pack_start(vpaned, True, True) vbox.pack_start(hbox, False, False) frame.add(vbox) frame.show_all() hbox = gtk.HBox() lbl = gtk.Label(toutf(os.path.basename(path) + '@' + revid)) close = self.create_tab_close_button() close.connect('clicked', self.close_page, frame) hbox.pack_start(lbl, True, True, 2) hbox.pack_start(close, False, False) hbox.show_all() num = self.notebook.append_page_menu(frame, hbox, gtk.Label(toutf(path + '@' + revid))) if hasattr(self.notebook, 'set_tab_reorderable'): self.notebook.set_tab_reorderable(frame, True) self.notebook.set_current_page(num) graphview.connect('revision-selected', self.log_selection_changed, path, followlabel, follow) objs = (frame, treeview.get_model(), path) graphview.treeview.connect('row-activated', self.log_activate, objs) graphview.treeview.connect('button-release-event', self._ann_button_release) graphview.treeview.connect('popup-menu', self._ann_popup_menu)