class RecoveryDialog(gtk.Window): def __init__(self, cwd='', root=''): """ Initialize the Dialog. """ gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) set_tortoise_icon(self, 'general.ico') self.root = root self.cwd = cwd self.selected_path = None self.hgthread = None self.connect('delete-event', self._delete) self.set_default_size(600, 400) name = os.path.basename(os.path.abspath(root)) self.set_title("TortoiseHg Recovery - " + name) # toolbar self.tbar = gtk.Toolbar() self.tips = gtk.Tooltips() self._stop_button = self._toolbutton(gtk.STOCK_STOP, 'Stop', self._stop_clicked, tip='Stop the hg operation') self._stop_button.set_sensitive(False) tbuttons = [ self._toolbutton(gtk.STOCK_CLEAR, 'clean', self._clean_clicked, tip='Clean checkout, undo all changes'), gtk.SeparatorToolItem(), self._toolbutton(gtk.STOCK_UNDO, 'rollback', self._rollback_clicked, tip='Rollback (undo) last transaction to' ' repository (pull, commit, etc)'), gtk.SeparatorToolItem(), self._toolbutton(gtk.STOCK_CLEAR, 'recover', self._recover_clicked, tip='Recover from interrupted operation'), gtk.SeparatorToolItem(), self._toolbutton(gtk.STOCK_APPLY, 'verify', self._verify_clicked, tip='Validate repository consistency'), gtk.SeparatorToolItem(), self._stop_button, gtk.SeparatorToolItem(), ] for btn in tbuttons: self.tbar.insert(btn, -1) sep = gtk.SeparatorToolItem() sep.set_expand(True) sep.set_draw(False) self.tbar.insert(sep, -1) button = self._toolbutton(gtk.STOCK_CLOSE, 'Close', self._close_clicked, tip='Close Application') self.tbar.insert(button, -1) vbox = gtk.VBox() self.add(vbox) vbox.pack_start(self.tbar, False, False, 2) # hg output window scrolledwindow = gtk.ScrolledWindow() scrolledwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN) scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = gtk.TextView(buffer=None) self.textview.set_editable(False) self.textview.modify_font(pango.FontDescription("Monospace")) scrolledwindow.add(self.textview) self.textview.set_editable(False) self.textbuffer = self.textview.get_buffer() vbox.pack_start(scrolledwindow, True, True) self.stbar = gtklib.StatusBar() vbox.pack_start(self.stbar, False, False, 2) def _close_clicked(self, *args): self._do_close() def _delete(self, widget, event): self._do_close() return True def _do_close(self): if self._cmd_running(): error_dialog(self, "Can't close now", "command is running") else: gtk.main_quit() def _toolbutton(self, stock, label, handler, menu=None, userdata=None, tip=None): if menu: tbutton = gtk.MenuToolButton(stock) tbutton.set_menu(menu) else: tbutton = gtk.ToolButton(stock) tbutton.set_label(label) if tip: tbutton.set_tooltip(self.tips, tip) tbutton.connect('clicked', handler, userdata) return tbutton def _clean_clicked(self, toolbutton, data=None): response = question_dialog(self, "Clean repository", "%s ?" % os.path.basename(self.root)) if not response == gtk.RESPONSE_YES: return try: repo = hg.repository(ui.ui(), path=self.root) except RepoError: self.write("Unable to find repo at %s\n" % (self.root), False) return pl = repo.workingctx().parents() cmd = ['update', '--clean', '--rev', str(pl[0].rev())] self._exec_cmd(cmd, postfunc=self._notify) def _notify(self, ret, *args): import time time.sleep(0.5) # give fs some time to pick up changes shell_notify([self.cwd]) def _rollback_clicked(self, toolbutton, data=None): response = question_dialog(self, "Rollback repository", "%s ?" % os.path.basename(self.root)) if not response == gtk.RESPONSE_YES: return cmd = ['rollback'] self._exec_cmd(cmd, postfunc=self._notify) def _recover_clicked(self, toolbutton, data=None): cmd = ['recover'] self._exec_cmd(cmd) def _verify_clicked(self, toolbutton, data=None): cmd = ['verify'] self._exec_cmd(cmd) def _stop_clicked(self, toolbutton, data=None): if self.hgthread and self.hgthread.isAlive(): self.hgthread.terminate() self._stop_button.set_sensitive(False) def _exec_cmd(self, cmd, postfunc=None): if self._cmd_running(): error_dialog(self, "Can't run now", "Pleas try again after the previous command is completed") return self._stop_button.set_sensitive(True) cmdline = cmd cmdline.append('--verbose') cmdline.append('--repository') cmdline.append(self.root) # show command to be executed self.write("", False) # execute command and show output on text widget gobject.timeout_add(10, self.process_queue) self.hgthread = HgThread(cmdline, postfunc) self.hgthread.start() self.stbar.begin() self.stbar.set_status_text('hg ' + ' '.join(cmdline)) def _cmd_running(self): if self.hgthread and self.hgthread.isAlive(): return True else: return False def write(self, msg, append=True): msg = toutf(msg) if append: enditer = self.textbuffer.get_end_iter() self.textbuffer.insert(enditer, msg) else: self.textbuffer.set_text(msg) def process_queue(self): """ Handle all the messages currently in the queue (if any). """ self.hgthread.process_dialogs() while self.hgthread.getqueue().qsize(): try: msg = self.hgthread.getqueue().get(0) self.write(msg) except Queue.Empty: pass if self._cmd_running(): return True else: self.stbar.end() self._stop_button.set_sensitive(False) if self.hgthread.return_code() is None: self.write("[command interrupted]") return False # Stop polling this function
class SynchDialog(gtk.Window): def __init__(self, cwd='', root = '', repos=[]): """ Initialize the Dialog. """ gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) shlib.set_tortoise_icon(self, 'menusynch.ico') self.root = root self.cwd = cwd self.selected_path = None self.hgthread = None # persistent app data self._settings = shlib.Settings('synch') self._recent_src = self._settings.mrul('src_paths') self.set_default_size(610, 400) self.paths = self._get_paths() self.origchangecount = self.repo.changelog.count() # load the fetch extension explicitly extensions.load(self.ui, 'fetch', None) name = self.repo.ui.config('web', 'name') or os.path.basename(root) self.set_title("TortoiseHg Synchronize - " + name) self.connect('delete-event', self._delete) # toolbar self.tbar = gtk.Toolbar() self.tips = gtk.Tooltips() self._stop_button = self._toolbutton(gtk.STOCK_STOP, 'Stop', self._stop_clicked, tip='Stop the hg operation') self._stop_button.set_sensitive(False) tbuttons = [ self._toolbutton(gtk.STOCK_GO_DOWN, 'Incoming', self._incoming_clicked, tip='Display changes that can be pulled' ' from selected repository'), self._toolbutton(gtk.STOCK_GOTO_BOTTOM, ' Pull ', self._pull_clicked, self._pull_menu(), tip='Pull changes from selected' ' repository'), gtk.SeparatorToolItem(), self._toolbutton(gtk.STOCK_GO_UP, 'Outgoing', self._outgoing_clicked, tip='Display local changes that will be pushed' ' to selected repository'), self._toolbutton(gtk.STOCK_GOTO_TOP, 'Push', self._push_clicked, tip='Push local changes to selected' ' repository'), self._toolbutton(gtk.STOCK_GOTO_LAST, 'Email', self._email_clicked, tip='Email local outgoing changes to' ' one or more recipients'), gtk.SeparatorToolItem(), self._stop_button, gtk.SeparatorToolItem(), self._toolbutton(gtk.STOCK_PREFERENCES, 'Configure', self._conf_clicked, tip='Configure peer repository paths'), gtk.SeparatorToolItem(), ] for btn in tbuttons: self.tbar.insert(btn, -1) sep = gtk.SeparatorToolItem() sep.set_expand(True) sep.set_draw(False) self.tbar.insert(sep, -1) button = self._toolbutton(gtk.STOCK_CLOSE, 'Quit', self._close_clicked, tip='Quit Application') self.tbar.insert(button, -1) vbox = gtk.VBox() self.add(vbox) vbox.pack_start(self.tbar, False, False, 2) # revision input revbox = gtk.HBox() lbl = gtk.Button("Remote Path:") lbl.unset_flags(gtk.CAN_FOCUS) lbl.connect('clicked', self._btn_remotepath_clicked) # revisions combo box self.pathlist = gtk.ListStore(str) self._pathbox = gtk.ComboBoxEntry(self.pathlist, 0) self._pathtext = self._pathbox.get_child() defrow = None defpushrow = None for row, (name, path) in enumerate(self.paths): if name == 'default': defrow = row if defpushrow is None: defpushrow = row elif name == 'default-push': defpushrow = row self.pathlist.append([path]) if repos: self._pathtext.set_text(repos[0]) elif defrow is not None: self._pathbox.set_active(defrow) elif defpushrow is not None: self._pathbox.set_active(defpushrow) sympaths = [x[1] for x in self.paths] for p in self._recent_src: if p not in sympaths: self.pathlist.append([p]) # create checkbox to disable proxy self._use_proxy = gtk.CheckButton("use proxy server") if ui.ui().config('http_proxy', 'host', ''): self._use_proxy.set_active(True) else: self._use_proxy.set_sensitive(False) revbox.pack_start(lbl, False, False) revbox.pack_start(self._pathbox, True, True) revbox.pack_end(self._use_proxy, False, False) vbox.pack_start(revbox, False, False, 2) expander = gtk.Expander('Advanced Options') expander.set_expanded(False) hbox = gtk.HBox() expander.add(hbox) revvbox = gtk.VBox() revhbox = gtk.HBox() self._reventry = gtk.Entry() self._force = gtk.CheckButton('Force pull or push') self.tips.set_tip(self._force, 'Run even when remote repository' ' is unrelated.') revhbox.pack_start(gtk.Label('Target Revision:'), False, False, 2) revhbox.pack_start(self._reventry, True, True, 2) eventbox = gtk.EventBox() eventbox.add(revhbox) self.tips.set_tip(eventbox, 'A specific revision up to which you' ' would like to push or pull.') revvbox.pack_start(eventbox, True, True, 8) revvbox.pack_start(self._force, False, False, 2) hbox.pack_start(revvbox, True, True, 4) frame = gtk.Frame('Incoming/Outgoing') hbox.pack_start(frame, False, False, 2) self._showpatch = gtk.CheckButton('Show Patches') self._newestfirst = gtk.CheckButton('Show Newest First') self._nomerge = gtk.CheckButton('Show No Merges') hbox = gtk.HBox() hbox.pack_start(self._showpatch, False, False, 2) hbox.pack_start(self._newestfirst, False, False, 2) hbox.pack_start(self._nomerge, False, False, 2) frame.add(hbox) vbox.pack_start(expander, False, False, 2) # hg output window scrolledwindow = gtk.ScrolledWindow() scrolledwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN) scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = gtk.TextView(buffer=None) self.textview.set_editable(False) self.textview.modify_font(pango.FontDescription("Monospace")) scrolledwindow.add(self.textview) self.textview.set_editable(False) self.textbuffer = self.textview.get_buffer() vbox.pack_start(scrolledwindow, True, True) self.buttonhbox = gtk.HBox() self.viewpulled = gtk.Button('View Pulled Revisions') self.viewpulled.connect('clicked', self._view_pulled_changes) self.updatetip = gtk.Button('Update to Tip') self.updatetip.connect('clicked', self._update_to_tip) self.buttonhbox.pack_start(self.viewpulled, False, False, 2) self.buttonhbox.pack_start(self.updatetip, False, False, 2) vbox.pack_start(self.buttonhbox, False, False, 2) self.stbar = gtklib.StatusBar() vbox.pack_start(self.stbar, False, False, 2) self.connect('map', self.update_buttons) def update_buttons(self, *args): self.buttonhbox.hide() self.repo.invalidate() tip = self.repo.changelog.count() if self.origchangecount == tip: self.viewpulled.hide() else: self.buttonhbox.show() self.viewpulled.show() self.repo.dirstate.invalidate() parent = self.repo.workingctx().parents()[0].rev() if parent == tip-1: self.updatetip.hide() else: self.buttonhbox.show() self.updatetip.show() def _view_pulled_changes(self, button): from history import GLog revs = (self.repo.changelog.count()-1, self.origchangecount) opts = {'revrange' : revs} dialog = GLog(self.ui, self.repo, self.cwd, [], opts, False) dialog.display() def _update_to_tip(self, button): self.repo.invalidate() wc = self.repo.workingctx() pl = wc.parents() p1, p2 = pl[0], self.repo.changectx('tip') pa = p1.ancestor(p2) warning = '' flags = [] if len(pl) > 1: warning = "Outstanding uncommitted merges" elif pa != p1 and pa != p2: warning = "Update spans branches" if warning: flags = ['--clean'] msg = 'Lose all changes in your working directory?' warning += ', requires clean checkout' if question_dialog(self, msg, warning) != gtk.RESPONSE_YES: return self.write("", False) # execute command and show output on text widget gobject.timeout_add(10, self.process_queue) cmdline = ['update', '-v', '-R', self.repo.root] + flags + ['tip'] self.hgthread = HgThread(cmdline) self.hgthread.start() self.stbar.begin() self.stbar.set_status_text('hg ' + ' '.join(cmdline)) def _pull_menu(self): menu = gtk.Menu() self._pull_fetch = gtk.CheckMenuItem("Do fetch") menu.append(self._pull_fetch) self._pull_update = gtk.CheckMenuItem("Update to new tip") menu.append(self._pull_update) # restore states from previous session st = self._settings.get_value('_pull_update_state', False) self._pull_update.set_active(st) menu.show_all() return menu def _get_paths(self, sort="value"): """ retrieve symbolic paths """ try: self.ui = ui.ui() self.repo = hg.repository(self.ui, path=self.root) paths = self.repo.ui.configitems('paths') if sort: if sort == "value": sortfunc = lambda a,b: cmp(a[1], b[1]) elif sort == "name": sortfunc = lambda a,b: cmp(a[0], b[0]) else: raise "unknown sort key '%s'" % sort paths.sort(sortfunc) return paths except RepoError: return None def _btn_remotepath_clicked(self, button): """ select source folder to clone """ dialog = gtk.FileChooserDialog(title="Select Repository", 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.root) response = dialog.run() if response == gtk.RESPONSE_OK: self._pathtext.set_text(dialog.get_filename()) dialog.destroy() def _close_clicked(self, toolbutton, data=None): self._do_close() def _do_close(self): if self._cmd_running(): error_dialog(self, "Can't close now", "command is running") else: self._save_settings() gtk.main_quit() def _save_settings(self): self._settings.set_value('_pull_update_state', self._pull_update.get_active()) self._settings.write() def _delete(self, widget, event): self._do_close() return True def _toolbutton(self, stock, label, handler, menu=None, userdata=None, tip=None): if menu: tbutton = gtk.MenuToolButton(stock) tbutton.set_menu(menu) else: tbutton = gtk.ToolButton(stock) tbutton.set_label(label) if tip: tbutton.set_tooltip(self.tips, tip) tbutton.connect('clicked', handler, userdata) return tbutton def _get_advanced_options(self): opts = {} if self._showpatch.get_active(): opts['patch'] = ['--patch'] if self._nomerge.get_active(): opts['no-merges'] = ['--no-merges'] if self._force.get_active(): opts['force'] = ['--force'] if self._newestfirst.get_active(): opts['newest-first'] = ['--newest-first'] target_rev = self._reventry.get_text().strip() if target_rev != "": opts['rev'] = ['--rev', target_rev] return opts def _pull_clicked(self, toolbutton, data=None): aopts = self._get_advanced_options() if self._pull_fetch.get_active(): cmd = ['fetch', '--message', 'merge'] else: cmd = ['pull'] cmd += aopts.get('force', []) if self._pull_update.get_active(): cmd.append('--update') cmd += aopts.get('rev', []) self._exec_cmd(cmd) def _push_clicked(self, toolbutton, data=None): aopts = self._get_advanced_options() cmd = ['push'] cmd += aopts.get('rev', []) cmd += aopts.get('force', []) self._exec_cmd(cmd) def _conf_clicked(self, toolbutton, data=None): newpath = self._pathtext.get_text() for name, path in self.paths: if path == newpath: newpath = None break from thgconfig import ConfigDialog dlg = ConfigDialog(self.root, True) dlg.show_all() if newpath: dlg.new_path(newpath) else: dlg.focus_field('paths.default') dlg.run() dlg.hide() self.paths = self._get_paths() self.pathlist.clear() for row, (name, path) in enumerate(self.paths): self.pathlist.append([path]) def _email_clicked(self, toolbutton, data=None): path = self._pathtext.get_text() if not path: info_dialog(self, 'No repository selected', 'Select a peer repository to compare with') self._pathbox.grab_focus() return from hgemail import EmailDialog dlg = EmailDialog(self.root, ['--outgoing', path]) dlg.set_transient_for(self) dlg.show_all() dlg.present() dlg.set_transient_for(None) def _incoming_clicked(self, toolbutton, data=None): aopts = self._get_advanced_options() cmd = ['incoming'] cmd += aopts.get('rev', []) cmd += aopts.get('patch', []) cmd += aopts.get('no-merges', []) cmd += aopts.get('force', []) cmd += aopts.get('newest-first', []) self._exec_cmd(cmd) def _outgoing_clicked(self, toolbutton, data=None): aopts = self._get_advanced_options() cmd = ['outgoing'] cmd += aopts.get('rev', []) cmd += aopts.get('patch', []) cmd += aopts.get('no-merges', []) cmd += aopts.get('force', []) cmd += aopts.get('newest-first', []) self._exec_cmd(cmd) def _stop_clicked(self, toolbutton, data=None): if self._cmd_running(): self.hgthread.terminate() self._stop_button.set_sensitive(False) def _exec_cmd(self, cmd): if self._cmd_running(): error_dialog(self, "Can't run now", "Pleas try again after the previous command is completed") return self._stop_button.set_sensitive(True) proxy_host = ui.ui().config('http_proxy', 'host', '') use_proxy = self._use_proxy.get_active() text_entry = self._pathbox.get_child() remote_path = str(text_entry.get_text()) cmdline = cmd[:] cmdline += ['--verbose', '--repository', self.root] if proxy_host and not use_proxy: cmdline += ["--config", "http_proxy.host="] cmdline += [remote_path] # show command to be executed self.write("", False) # execute command and show output on text widget gobject.timeout_add(10, self.process_queue) self.hgthread = HgThread(cmdline, parent=self) self.hgthread.start() self.stbar.begin() self.stbar.set_status_text('hg ' + ' '.join(cmd + [remote_path])) self._add_src_to_recent(remote_path) def _cmd_running(self): if self.hgthread and self.hgthread.isAlive(): return True else: return False def _add_src_to_recent(self, src): if os.path.exists(src): src = os.path.abspath(src) # save path to recent list in history self._recent_src.add(src) self._settings.write() # update drop-down list self.pathlist.clear() sympaths = [x[1] for x in ui.ui().configitems('paths')] paths = list(set(sympaths + [x for x in self._recent_src])) paths.sort() for p in paths: self.pathlist.append([p]) def write(self, msg, append=True): msg = toutf(msg) if append: enditer = self.textbuffer.get_end_iter() self.textbuffer.insert(enditer, msg) self.textview.scroll_to_mark(self.textbuffer.get_insert(), 0) else: self.textbuffer.set_text(msg) def process_queue(self): """ Handle all the messages currently in the queue (if any). """ self.hgthread.process_dialogs() while self.hgthread.getqueue().qsize(): try: msg = self.hgthread.getqueue().get(0) self.write(msg) except Queue.Empty: pass if self._cmd_running(): return True else: # Update button states self.update_buttons() self.stbar.end() self._stop_button.set_sensitive(False) if self.hgthread.return_code() is None: self.write("[command interrupted]") return False # Stop polling this function
class CmdDialog(gtk.Dialog): def __init__(self, cmdline, progressbar=True, width=520, height=400): title = 'hg ' + ' '.join(cmdline[1:]) gtk.Dialog.__init__(self, title=title, flags=gtk.DIALOG_MODAL, #buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) ) set_tortoise_icon(self, 'hg.ico') self.cmdline = cmdline self.returncode = None # construct dialog self.set_default_size(width, height) self._button_stop = gtk.Button("Stop") self._button_stop.connect('clicked', self._on_stop_clicked) self.action_area.pack_start(self._button_stop) self._button_ok = gtk.Button("Close") self._button_ok.connect('clicked', self._on_ok_clicked) self.action_area.pack_start(self._button_ok) self.connect('delete-event', self._delete) self.connect('response', self._response) self.pbar = None if progressbar: self.last_pbar_update = 0 hbox = gtk.HBox() self.status_text = gtk.Label() self.status_text.set_text(toutf(" ".join(cmdline).replace("\n", " "))) self.status_text.set_alignment(0, 0.5) self.status_text.set_ellipsize(pango.ELLIPSIZE_END) hbox.pack_start(self.status_text, True, True, 3) # Create a centering alignment object align = gtk.Alignment(0.0, 0.0, 1, 0) hbox.pack_end(align, False, False, 3) align.show() # create the progress bar self.pbar = gtk.ProgressBar() align.add(self.pbar) self.pbar.pulse() self.pbar.show() self.vbox.pack_start(hbox, False, False, 3) scrolledwindow = gtk.ScrolledWindow() scrolledwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN) scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = gtk.TextView(buffer=None) self.textview.set_editable(False) self.textview.modify_font(pango.FontDescription("Monospace")) scrolledwindow.add(self.textview) self.textbuffer = self.textview.get_buffer() self.vbox.pack_start(scrolledwindow, True, True) self.connect('map_event', self._on_window_map_event) self.show_all() def _on_ok_clicked(self, button): """ Ok button clicked handler. """ self.response(gtk.RESPONSE_ACCEPT) def _on_stop_clicked(self, button): self.hgthread.terminate() def _delete(self, widget, event): return True def _response(self, widget, response_id): if self.hgthread.isAlive(): widget.emit_stop_by_name('response') def _on_window_map_event(self, event, param): self.hgthread = HgThread(self.cmdline[1:]) self.hgthread.start() self._button_ok.set_sensitive(False) self._button_stop.set_sensitive(True) gobject.timeout_add(10, self.process_queue) def write(self, msg, append=True): msg = toutf(msg) if append: enditer = self.textbuffer.get_end_iter() self.textbuffer.insert(enditer, msg) else: self.textbuffer.set_text(msg) def process_queue(self): """ Handle all the messages currently in the queue (if any). """ self.hgthread.process_dialogs() enditer = self.textbuffer.get_end_iter() while self.hgthread.getqueue().qsize(): try: msg = self.hgthread.getqueue().get(0) self.textbuffer.insert(enditer, toutf(msg)) self.textview.scroll_to_mark(self.textbuffer.get_insert(), 0) except Queue.Empty: pass self.update_progress() if not self.hgthread.isAlive(): self._button_ok.set_sensitive(True) self._button_stop.set_sensitive(False) self.returncode = self.hgthread.return_code() if self.returncode is None: self.write("\n[command interrupted]") return False # Stop polling this function else: return True def update_progress(self): if not self.pbar: return # progress bar not enabled if not self.hgthread.isAlive(): self.pbar.unmap() else: # pulse the progress bar every ~100ms tm = get_system_times()[4] if tm - self.last_pbar_update < 0.100: return self.last_pbar_update = tm self.pbar.pulse() def return_code(self): return self.hgthread.return_code()