Source

my thg / tortoisehg / hgtk / clone.py

# clone.py - Clone dialog for TortoiseHg
#
# Copyright 2007 TK Soh <teekaysoh@gmail.com>
# Copyright 2007 Steve Borho <steve@borho.org>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

import os
import gtk
import pango

from mercurial import extensions

from tortoisehg.util.i18n import _
from tortoisehg.util import hglib, shlib, settings
from tortoisehg.hgtk import gdialog, gtklib

class CloneDialog(gdialog.GDialog):
    """ Dialog to clone a Mercurial repo """
    def __init__(self, repos=[]):
        gdialog.GDialog.__init__(self, norepo=True)

        srcpath = hglib.toutf(os.getcwd())
        destpath = srcpath
        if len(repos) > 1:
            srcpath = repos[0]
            destpath = repos[1]
        elif len(repos):
            srcpath = repos[0]
        self.srcpath = srcpath
        self.destpath = destpath

    ### Start of Overriding Section ###

    def get_title(self, *args):
        return _('TortoiseHg Clone')

    def get_icon(self):
        return 'menuclone.ico'

    def get_setting_name(self):
        return 'clone'

    def get_body(self, vbox):
        # MRU lists
        self.recentsrc = self.settings.mrul('src_paths')
        self.recentdest = self.settings.mrul('dest_paths')

        def createcombo(path, label, title, bundle=False):
            # comboentry
            model = gtk.ListStore(str)
            combo = gtk.ComboBoxEntry(model, 0)
            combo.set_size_request(280, -1)
            combo.size_request()
            entry = combo.get_child()
            entry.set_text(path)
            entry.set_position(-1)
            entry.connect('activate',
                          lambda b: self.response(gtk.RESPONSE_OK))

            # replace the drop-down widget so we can modify it's properties
            combo.clear()
            cell = gtk.CellRendererText()
            cell.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE)
            combo.pack_start(cell)
            combo.add_attribute(cell, 'text', 0)

            # browse button
            browse = gtk.Button(_('Browse...'))
            browse.connect('clicked', self.browse_clicked, title, entry)

            if bundle:
                # bundle button
                bundlebtn = gtk.Button(_('Bundle...'))
                bundlebtn.connect('clicked', self.bundle_clicked, 
                                  _('Select a Mercurial Bundle'), entry)
                table.add_row(label, combo, 0, browse, bundlebtn)
            else:
                table.add_row(label, combo, 0, browse)

            return model, combo

        # layout table for fixed options
        self.table = table = gtklib.LayoutTable()
        vbox.pack_start(table, False, False, 2)

        ## comboentry for source paths
        self.srclist, srccombo = createcombo(self.srcpath,
                                             _('Source path:'),
                                             _('Select Source Folder'), True)
        self.srcentry = srccombo.get_child()

        ## add pre-defined src paths to pull-down list
        sync_src = settings.Settings('synch').mrul('src_paths')
        sympaths = [x[1] for x in self.ui.configitems('paths')]
        recent = [x for x in self.recentsrc]
        syncsrc = [x for x in sync_src]
        paths = list(set(sympaths + recent + syncsrc))
        paths.sort()
        for p in paths:
            self.srclist.append([p])

        ## comboentry for destination paths
        self.destlist, destcombo = createcombo(self.destpath,
                                               _('Destination path:'),
                                               _('Select Destination Folder'))
        self.destentry = destcombo.get_child()

        ## add most-recent dest paths to pull-down list
        paths = list(self.recentdest)
        paths.sort()
        for p in paths:
            self.destlist.append([p])

        # expander for advanced options
        self.expander = expander = gtk.Expander(_('Advanced options'))
        vbox.pack_start(expander, False, False, 2)

        # layout table for advanced options
        table = gtklib.LayoutTable()
        expander.add(table)

        ## revision option
        self.reventry = gtk.Entry()
        self.reventry.set_sensitive(False)
        self.optrev = gtk.CheckButton(_('Clone to revision:'))
        self.optrev.connect('toggled', self.checkbutton_toggled, self.reventry)
        table.add_row(self.optrev, self.reventry)

        self.exs = [name for name, module in extensions.extensions()]
        if ('perfarce' in self.exs) or ('hgsubversion' in self.exs):
            self.startreventry = gtk.Entry()
            self.startreventry.set_sensitive(False)
            self.optstartrev = gtk.CheckButton(_('Starting Revision:'))
            self.optstartrev.connect('toggled',
                    self.checkbutton_toggled, self.startreventry)
            table.add_row(self.optstartrev, self.startreventry)

        ## options
        self.optupdate = gtk.CheckButton(_('Do not update the new working directory'))
        self.optpull = gtk.CheckButton(_('Use pull protocol to copy metadata'))
        self.optuncomp = gtk.CheckButton(_('Use uncompressed transfer'))
        table.add_row(self.optupdate)
        table.add_row(self.optpull)
        table.add_row(self.optuncomp)

        ## proxy options
        self.optproxy = gtk.CheckButton(_('Use proxy server'))
        table.add_row(self.optproxy)
        if self.ui.config('http_proxy', 'host'):
            self.optproxy.set_active(True)
        else:
            self.optproxy.set_sensitive(False)

        ## remote cmd option
        self.remotecmdentry = gtk.Entry()
        self.remotecmdentry.set_sensitive(False)
        self.optremote = gtk.CheckButton(_('Remote command:'))
        self.optremote.connect('toggled', self.checkbutton_toggled, self.remotecmdentry)
        table.add_row(self.optremote)
        table.add_row(self.remotecmdentry, padding=False)

    def get_buttons(self):
        return [('clone', _('Clone'), gtk.RESPONSE_OK),
                ('cancel', gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE)]

    def get_default_button(self):
        return 'clone'

    def get_action_map(self):
        return {gtk.RESPONSE_OK: self.clone}

    def switch_to(self, normal, working, *args):
        self.table.set_sensitive(normal)
        self.expander.set_sensitive(normal)
        self.buttons['clone'].set_property('visible', normal)
        self.buttons['cancel'].set_property('visible', normal)
        if normal:
            self.buttons['cancel'].grab_focus()
        if working:
            self.set_resizable(True)
            self.vbox.set_child_packing(self.cmd, True, True, 6, gtk.PACK_START)

    def command_done(self, returncode, useraborted, src, dest):
        self.add_src_to_recent(src)
        self.add_dest_to_recent(dest)
        if returncode == 0:
            if dest:
                shlib.shell_notify([dest])
            self.cmd.set_result(_('Cloned successfully'), style='ok')
        elif useraborted:
            self.cmd.set_result(_('Canceled updating'), style='error')
        else:
            self.cmd.set_result(_('Failed to clone'), style='error')

    def load_settings(self):
        expanded = self.settings.get_value('expanded', False, True)
        self.expander.set_property('expanded', expanded)

    def store_settings(self):
        expanded = self.expander.get_property('expanded')
        self.settings.set_value('expanded', expanded)
        self.settings.write()

    ### End of Overriding Section ###

    def browse_clicked(self, button, title, entry):
        res = gtklib.NativeFolderSelectDialog(
                     initial=entry.get_text().strip(), title=title).run()
        if res:
            entry.set_text(res)
            entry.grab_focus()
            entry.set_position(-1)

    def bundle_clicked(self, button, title, entry):
        path = entry.get_text().strip()
        if os.path.isdir(path):
            initial = path
        else:
            initial = os.path.dirname(path)

        res = gtklib.NativeSaveFileDialogWrapper(
                     initial=initial,
                     title=title, 
                     filter= ((_('Mercurial bundles'), '*.hg'),),
                     open=True).run()
        if res:
            entry.set_text(res)

    def checkbutton_toggled(self, checkbutton, entry):
        state = checkbutton.get_active()
        entry.set_sensitive(state)
        if state:
            entry.grab_focus()

    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.recentsrc.add(src)
        self.settings.write()

        # update drop-down list
        self.srclist.clear()
        sympaths = [x[1] for x in self.ui.configitems('paths')]
        paths = list(set(sympaths + [x for x in self.recentsrc]))
        paths.sort()
        for p in paths:
            self.srclist.append([p])

    def add_dest_to_recent(self, dest):
        if not dest:
            return
        if os.path.exists(dest):
            dest = os.path.abspath(dest)

        # save path to recent list in history
        self.recentdest.add(dest)
        self.settings.write()

        # update drop down list
        paths = list(self.recentdest)
        paths.sort()
        self.destlist.clear()
        for p in paths:
            self.destlist.append([p])

    def clone(self):
        # gather input data
        src = self.srcentry.get_text().strip()
        dest = self.destentry.get_text().strip() or os.path.basename(src)
        remotecmd = self.remotecmdentry.get_text().strip()
        if self.reventry.get_property('sensitive'):
            rev = self.reventry.get_text().strip()
        else:
            rev = None

        if hasattr(self, 'startreventry') and \
                   self.startreventry.get_property('sensitive'):
            startrev = self.startreventry.get_text().strip()
        else:
            startrev = None

        # verify input
        if src == '':
            gdialog.Prompt(_('Source path is empty'),
                    _('Please enter a valid source path'), self).run()
            self.srcentry.grab_focus()
            return False

        if src == dest:
            gdialog.Prompt(_('Source and destination are the same'),
                    _('Please specify different paths'), self).run()
            self.destentry.grab_focus()
            return False

        if dest == os.getcwd():
            if os.listdir(dest):
                # cur dir has files, specify no dest, let hg take
                # basename
                dest = None
            else:
                dest = '.'
        else:
            abs = os.path.abspath(dest)
            dirabs = os.path.dirname(abs)
            if dirabs == src:
                dest = os.path.join(os.path.dirname(dirabs), dest)

        # prepare command line
        cmdline = ['hg', 'clone']
        if self.optupdate.get_active():
            cmdline.append('--noupdate')
        if self.optuncomp.get_active():
            cmdline.append('--uncompressed')
        if self.optpull.get_active():
            cmdline.append('--pull')
        if self.ui.config('http_proxy', 'host'):
            if not self.optproxy.get_active():
                cmdline += ['--config', 'http_proxy.host=']
        if remotecmd:
            cmdline.append('--remotecmd')
            cmdline.append(hglib.fromutf(remotecmd))
        if rev:
            cmdline.append('--rev')
            cmdline.append(rev)
        if startrev:
            cmdline.append('--startrev')
            cmdline.append(startrev)

        cmdline.append('--verbose')
        cmdline.append(hglib.fromutf(src))
        if dest:
            cmdline.append('--')
            cmdline.append(hglib.fromutf(dest))

        # start cloning
        self.execute_command(cmdline, src, dest)

def run(_ui, *pats, **opts):
    return CloneDialog(pats)