hggtk: add synch dialog to handle pull/push/in/out
authorTK Soh <teekaysoh@yahoo.com>
Tue, 11 Dec 2007 03:36:14 +0000
changeset 343 2fb42543dcd5
parent 337 1f161ca182e3
child 344 742c79e0038a
hggtk: add synch dialog to handle pull/push/in/out
hggtk/synch.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hggtk/synch.py	Tue Dec 11 03:36:14 2007 +0000
@@ -0,0 +1,334 @@
+#
+# Repository synchronization dialog for TortoiseHg
+#
+# Copyright (C) 2007 Steve Borho <steve@borho.org>
+# Copyright (C) 2007 TK Soh <teekaysoh@gmail.com>
+#
+
+try:
+    import pygtk
+    pygtk.require("2.0")
+except:
+    pass
+
+import gtk
+import gobject
+import pango
+import Queue
+import subprocess
+import os
+import threading
+from mercurial import hg, ui, util 
+from mercurial.node import *
+from dialog import error_dialog
+
+class SynchDialog(gtk.Dialog):
+    def __init__(self, root=''):
+        """ Initialize the Dialog. """
+        rootbase = os.path.basename(root)
+        gtk.Dialog.__init__(self, title="TortoiseHg Synchronize - %s" % rootbase,
+                                  parent=None,
+                                  flags=0,
+                                  buttons=())
+        self.root = root
+        self.selected_path = None
+        self.thread1 = None
+        self.queue = Queue.Queue()
+
+        try:
+            self.repo = hg.repository(ui.ui(), path=self.root)
+        except hg.RepoError:
+            return None
+
+        self.set_default_size(600, 400)
+
+        #self.connect('delete-event', lambda x, y: True)
+        self.connect('delete-event', self._delete)
+        self.connect('response', self._response)
+
+        self._btn_close = gtk.Button("Ok")
+        self._btn_close.connect('clicked', self._close_clicked)
+        self.action_area.pack_end(self._btn_close)
+
+        # toolbar
+        self.tbar = gtk.Toolbar()
+        tbuttons = [
+                self._toolbutton(gtk.STOCK_GO_DOWN,
+                                 'incoming', 
+                                 self._incoming_clicked,
+                                 self._incoming_menu()),
+                self._toolbutton(gtk.STOCK_GOTO_BOTTOM,
+                                 'pull',
+                                 self._pull_clicked,
+                                 self._pull_menu()),
+                gtk.SeparatorToolItem(),
+                self._toolbutton(gtk.STOCK_GO_UP,
+                                 'outgoing',
+                                 self._outgoing_clicked,
+                                 self._outgoing_menu()),
+                self._toolbutton(gtk.STOCK_GOTO_TOP,
+                                 'push',
+                                 self._push_clicked,
+                                 self._push_menu()),
+                gtk.SeparatorToolItem(),
+            ]
+        for btn in tbuttons:
+            self.tbar.insert(btn, -1)
+        self.vbox.pack_start(self.tbar, False, False, 2)
+        
+        # revision input
+        revbox = gtk.HBox()
+        lbl = gtk.Label("Remote path:")
+        
+        # revisions  combo box
+        revlist = gtk.ListStore(str)
+        self._pathbox = gtk.ComboBoxEntry(revlist, 0)
+        
+        self.paths = self._get_paths()
+        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
+            revlist.append([path])
+
+        if defrow:
+            self._pathbox.set_active(defrow)
+        elif defpushrow:
+            self._pathbox.set_active(defpushrow)
+    
+        revbox.pack_start(lbl, False, False)
+        revbox.pack_start(self._pathbox, True, True)
+        self.vbox.pack_start(revbox, 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()
+        self.vbox.pack_start(scrolledwindow, True, True)
+
+        self.vbox.show_all()
+        
+    def _pull_menu(self):
+        menu = gtk.Menu()
+           
+        self._pull_update = gtk.CheckMenuItem("Update to new tip")
+        menu.append(self._pull_update)
+
+        self._pull_force = gtk.CheckMenuItem("Force pull")
+        menu.append(self._pull_force)
+        
+        menu.show_all()
+        return menu
+        
+    def _push_menu(self):
+        menu = gtk.Menu()
+        
+        self._push_force = gtk.CheckMenuItem("Force push")
+        menu.append(self._push_force)
+        
+        menu.show_all()
+        return menu
+        
+    def _incoming_menu(self):
+        menu = gtk.Menu()
+
+        self._incoming_show_patch = gtk.CheckMenuItem("Show patch")
+        menu.append(self._incoming_show_patch)
+        
+        self._incoming_no_merges = gtk.CheckMenuItem("Do not show merges")
+        menu.append(self._incoming_no_merges)
+
+        self._incoming_newest = gtk.CheckMenuItem("Show newest record first")
+        menu.append(self._incoming_newest)
+
+        self._incoming_force = gtk.CheckMenuItem("Force incoming")
+        menu.append(self._incoming_force)
+        
+        menu.show_all()
+        return menu
+        
+    def _outgoing_menu(self):
+        menu = gtk.Menu()
+
+        self._outgoing_show_patch = gtk.CheckMenuItem("Show patch")
+        menu.append(self._outgoing_show_patch)
+        
+        self._outgoing_no_merges = gtk.CheckMenuItem("Do not show merges")
+        menu.append(self._outgoing_no_merges)
+
+        self._outgoing_newest = gtk.CheckMenuItem("Show newest record first")
+        menu.append(self._outgoing_newest)
+        
+        self._outgoing_force = gtk.CheckMenuItem("Force incoming")
+        menu.append(self._outgoing_force)
+        
+        menu.show_all()
+        return menu
+        
+    def _get_paths(self):
+        """ retrieve repo revisions """
+        try:
+            repo = hg.repository(ui.ui(), path=self.root)
+            return repo.ui.configitems('paths')
+        except hg.RepoError:
+            return None
+
+    def _close_clicked(self, *args):
+        if self.thread1 and self.thread1.isAlive():
+            error_dialog("Can't close now", "command is running")
+        else:
+            self.response(gtk.RESPONSE_CLOSE)
+        
+    def _delete(self, widget, event):
+        return True
+        
+    def _response(self, widget, response_id):
+        if self.thread1 and self.thread1.isAlive():
+            error_dialog("Can't close now", "command is running")
+            widget.emit_stop_by_name('response')
+    
+    def _toolbutton(self, stock, label, handler, menu=None, userdata=None):
+        if menu:
+            tbutton = gtk.MenuToolButton(stock)
+            tbutton.set_menu(menu)
+        else:
+            tbutton = gtk.ToolButton(stock)
+            
+        tbutton.set_label(label)
+        tbutton.connect('clicked', handler, userdata)
+        return tbutton
+        
+    def _pull_clicked(self, toolbutton, data=None):
+        cmd = 'pull'
+        if self._pull_update.get_active():
+            cmd += " --update"
+        if self._pull_force.get_active():
+            cmd += " --force"
+        self._exec_cmd(cmd)
+    
+    def _push_clicked(self, toolbutton, data=None):
+        cmd = 'push'
+        if self._push_force.get_active():
+            cmd += " --force"
+        self._exec_cmd(cmd)
+        
+    def _incoming_clicked(self, toolbutton, data=None):
+        cmd = 'incoming'
+        if self._incoming_show_patch.get_active():
+            cmd += " --patch"
+        if self._incoming_no_merges.get_active():
+            cmd += " --no-merges"
+        if self._incoming_force.get_active():
+            cmd += " --force"
+        if self._incoming_newest.get_active():
+            cmd += " --newest-first"
+        self._exec_cmd(cmd)
+        
+    def _outgoing_clicked(self, toolbutton, data=None):
+        cmd = 'outgoing'
+        if self._outgoing_show_patch.get_active():
+            cmd += " --patch"
+        if self._outgoing_no_merges.get_active():
+            cmd += " --no-merges"
+        if self._outgoing_force.get_active():
+            cmd += " --force"
+        if self._outgoing_newest.get_active():
+            cmd += " --newest-first"
+        self._exec_cmd(cmd)
+        
+    def _exec_cmd(self, cmd):
+        text_entry = self._pathbox.get_child()
+        remote_path = str(text_entry.get_text())
+        
+        self.cmdline = "hg %s --verbose --repository %s %s" % (cmd,
+                                                     util.shellquote(self.repo.root),
+                                                     util.shellquote(remote_path),
+                                                     )
+        
+        # show command to be executed
+        if type(self.cmdline) == type([]):
+            cmd = " ".join(self.cmdline)
+        else:
+            cmd = self.cmdline
+        
+        # execute comnand and show output on text widget
+        self.write("", False)
+        self.write("$ %s\n" % cmd)
+        self._start_thread()
+
+    def _start_thread(self):
+        gobject.timeout_add(10, self.process_queue)
+        self.thread1 = threading.Thread(target=self._do_popen)
+        self.thread1.start()
+        
+    def write(self, msg, append=True):
+        msg = unicode(msg, 'iso-8859-1')
+        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).
+        """
+        while self.queue.qsize():
+            try:
+                msg = self.queue.get(0)
+                self.write(msg)
+            except Queue.Empty:
+                pass
+                
+        return True
+        
+    def _do_popen(self):        
+        if not self.cmdline:
+            return
+
+        # run hg in unbuffered mode, so the output can be captured and display a.s.a.p.
+        os.environ['PYTHONUNBUFFERED'] = "1"
+
+        # start hg operation on a subprocess and capture the output
+        pop = subprocess.Popen(self.cmdline, 
+                               shell=True,
+                               stderr=subprocess.STDOUT,
+                               stdout=subprocess.PIPE,
+                               stdin=subprocess.PIPE)
+
+        try:
+            line = 0
+            blocksize = 1024
+            while pop.poll() == None:
+                if line < 100:
+                    out = pop.stdout.readline()
+                    line += 1
+                else:
+                    out = pop.stdout.read(blocksize)
+                    if blocksize < 1024 * 50:
+                        blocksize *= 2
+                self.queue.put(out)
+            out = pop.stdout.read()
+            self.queue.put(out)
+        except IOError:
+            pass
+
+def run(root=''):
+    dlg = SynchDialog(root=root)
+    dlg.run()
+    dlg.hide()
+    
+if __name__ == "__main__":
+    import sys
+    run()