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 _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()
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 _cmenu_display(self, menuitem): from changeset import ChangeSet statopts = {'rev' : [self.currev] } dialog = ChangeSet(self.ui, self.repo, self.cwd, [], statopts, False) dialog.display()
def diff_set( file_a, file_b, file_a_path, file_b_path, change_set_a: changeset.ChangeSet, change_set_b: changeset.ChangeSet, ): """ This function gets the diff between two files and adds each line to a change set. Flags are set for each lines depending on if the lines were changes, added, or are the same. :param file_a: left hand file to compare :param file_b: right hand file to compare :param change_set_a: change set object for the left file :param change_set_b: change set object for the right file :return: pmEnums.CHANGED value indicating if operation was successful """ file_a_lines: list = file_a.read().splitlines() file_b_lines: list = file_b.read().splitlines() last_vals: list = [0, 0] # Last found match indices # Get the raw, padded LCS output file_a_lines.append( "$" ) # Append a token on the end to make sure last 'lines' always match file_b_lines.append("$") raw_diff: list = longest_common_subseq.padded_lcs( file_a_lines, file_b_lines, max(len(file_a_lines), len(file_b_lines))) for n in range(len(raw_diff[0])): if raw_diff[0][n] != -1 and raw_diff[1][n] != -1: change_set_a.add_change(n, pymerge_enums.CHANGEDENUM.SAME, file_a_lines[raw_diff[0][n]]) change_set_b.add_change(n, pymerge_enums.CHANGEDENUM.SAME, file_b_lines[raw_diff[1][n]]) last_vals = [raw_diff[0][n], raw_diff[1][n]] else: # If the delta between the next match indices and the previous match indices is equal, just set to diff if ((0 < n < len(raw_diff[0]) - 1) and ((raw_diff[0][n - 1] + 2) == raw_diff[0][n + 1]) or ((raw_diff[1][n - 1] + 2) == raw_diff[1][n + 1])): change_set_a.add_change(n, pymerge_enums.CHANGEDENUM.CHANGED, file_a_lines[raw_diff[0][n - 1] + 1]) change_set_b.add_change(n, pymerge_enums.CHANGEDENUM.CHANGED, file_b_lines[raw_diff[1][n - 1] + 1]) else: # Get the next matching indices next_vals = get_next_idx_match(raw_diff, n) if next_vals == [-1, -1]: return pymerge_enums.RESULT.ERROR idx_delta = [ next_vals[0] - last_vals[0], next_vals[1] - last_vals[1] ] # If the match index deltas are equal the line flags can default to CHANGED if idx_delta[0] == idx_delta[1]: last_vals = [x + 1 for x in last_vals] change_set_a.add_change(n, pymerge_enums.CHANGEDENUM.CHANGED, file_a_lines[last_vals[0]]) change_set_b.add_change(n, pymerge_enums.CHANGEDENUM.CHANGED, file_b_lines[last_vals[1]]) # If the delta is greater on the left side, that means lines were inserted in the left file elif idx_delta[0] > idx_delta[1]: # Check if the last index matches are getting close to to the next index matches if last_vals[1] < (next_vals[1] - 1): last_vals = [x + 1 for x in last_vals] change_set_a.add_change( n, pymerge_enums.CHANGEDENUM.CHANGED, file_a_lines[last_vals[0]]) change_set_b.add_change( n, pymerge_enums.CHANGEDENUM.CHANGED, file_b_lines[last_vals[1]]) else: last_vals = [x + 1 for x in last_vals] change_set_a.add_change( n, pymerge_enums.CHANGEDENUM.CHANGED, file_a_lines[last_vals[0]]) change_set_b.add_change( n, pymerge_enums.CHANGEDENUM.ADDED, "") # if the delta is greater on the right side, that means lines were inserted in the right file elif idx_delta[0] < idx_delta[1]: if last_vals[0] < (next_vals[0] - 1): last_vals = [x + 1 for x in last_vals] change_set_a.add_change( n, pymerge_enums.CHANGEDENUM.CHANGED, file_a_lines[last_vals[0]]) change_set_b.add_change( n, pymerge_enums.CHANGEDENUM.CHANGED, file_b_lines[last_vals[1]]) else: last_vals = [x + 1 for x in last_vals] change_set_a.add_change( n, pymerge_enums.CHANGEDENUM.ADDED, "") change_set_b.add_change( n, pymerge_enums.CHANGEDENUM.CHANGED, file_b_lines[last_vals[1]]) else: # The default flag is CHANGED change_set_a.add_change(n, pymerge_enums.CHANGEDENUM.CHANGED, file_a_lines[raw_diff[0][n]]) change_set_b.add_change(n, pymerge_enums.CHANGEDENUM.CHANGED, file_b_lines[raw_diff[1][n]]) return pymerge_enums.RESULT.GOOD