Source

thg / tortoisehg / hgtk / archive.py

# archive.py - TortoiseHg's dialog for archiving a repo revision
#
# Copyright 2009 Emmanuel Rosa <goaway1000@gmail.com>
#
# 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

from mercurial import error

from tortoisehg.util.i18n import _
from tortoisehg.util import hglib

from tortoisehg.hgtk import gtklib, gdialog

WD_PARENT = _('= Working Directory Parent =')

class ArchiveDialog(gdialog.GDialog):
    """ Dialog to archive a Mercurial repo """
    def __init__(self, rev=None):
        gdialog.GDialog.__init__(self)
        self.initrev = rev

    ### Start of Overriding Section ###

    def get_title(self, reponame):
        return _('Archive - %s') % reponame

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

    def get_body(self, vbox):
        self.prevtarget = None

        # layout table
        self.table = table = gtklib.LayoutTable()
        vbox.pack_start(table, True, True, 2)

        ## revision combo
        self.combo = gtk.combo_box_entry_new_text()
        self.combo.child.set_width_chars(28)
        self.combo.child.connect('activate',
                                 lambda b: self.response(gtk.RESPONSE_OK))
        if self.initrev == '':
            self.combo.append_text(WD_PARENT)
        else:
            self.combo.append_text(str(self.initrev))
        self.combo.set_active(0)
        for b in self.repo.branchtags():
            self.combo.append_text(b)
        tags = list(self.repo.tags())
        tags.sort()
        tags.reverse()
        for t in tags:
            self.combo.append_text(t)

        table.add_row(_('Archive revision:'), self.combo)

        self.opt_files_in_rev = gtk.CheckButton(
                    _('Only files modified/created in this revision'))
        table.add_row('', self.opt_files_in_rev, ypad=0)

        ## dest combo & browse button
        self.destentry = gtk.Entry()
        self.destentry.set_width_chars(46)

        destbrowse = gtk.Button(_('Browse...'))
        destbrowse.connect('clicked', self.browse_clicked)

        table.add_row(_('Destination path:'), self.destentry, 0, destbrowse)

        ## archive types
        self.filesradio = gtk.RadioButton(None, _('Directory of files'))
        self.filesradio.connect('toggled', self.type_changed)
        table.add_row(_('Archive types:'), self.filesradio, ypad=0)
        def add_type(label):
            radio = gtk.RadioButton(self.filesradio, label)
            radio.connect('toggled', self.type_changed)
            table.add_row(None, radio, ypad=0)
            return radio
        self.tarradio = add_type(_('Uncompressed tar archive'))
        self.tbz2radio = add_type(_('Tar archive compressed using bzip2'))
        self.tgzradio = add_type(_('Tar archive compressed using gzip'))
        self.uzipradio = add_type(_('Uncompressed zip archive'))
        self.zipradio = add_type(_('Zip archive compressed using deflate'))

        # signal handler
        self.combo.connect('changed', lambda c: self.update_path())

        # prepare to show
        self.update_path(hglib.toutf(self.repo.root))

    def get_buttons(self):
        return [('archive', _('Archive'), gtk.RESPONSE_OK),
                ('close', gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)]

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

    def switch_to(self, normal, working, cmd):
        self.table.set_sensitive(normal)
        self.buttons['archive'].set_property('visible', normal)
        self.buttons['close'].set_property('visible', normal)
        if normal:
            self.buttons['close'].grab_focus()

    def command_done(self, returncode, useraborted, *args):
        if returncode == 0:
            self.cmd.set_result(_('Archived successfully'), style='ok')
        elif useraborted:
            self.cmd.set_result(_('Canceled archiving'), style='error')
        else:
            self.cmd.set_result(_('Failed to archive'), style='error')

    ### End of Overriding Section ###

    def type_changed(self, radio):
        if not radio.get_active():
            return
        self.update_path()

    def update_path(self, path=None):
        def remove_ext(path):
            for ext in ('.tar', '.tar.bz2', '.tar.gz', '.zip'):
                if path.endswith(ext):
                    return path.replace(ext, '')
            return path
        def remove_rev(path):
            model = self.combo.get_model()
            revs = [rev[0] for rev in model]
            revs.append(wdrev)
            if not self.prevtarget is None:
                revs.append(self.prevtarget)
            for rev in ['_' + rev for rev in revs]:
                if path.endswith(rev):
                    return path.replace(rev, '')
            return path
        def add_rev(path, rev):
            return '%s_%s' % (path, rev)
        def add_ext(path):
            select = self.get_selected_archive_type()
            if select['type'] != 'files':
                path += select['ext']
            return path
        text = self.combo.get_active_text()
        if len(text) == 0:
            return
        wdrev = str(self.repo['.'].rev())
        if text == WD_PARENT:
            text = wdrev
        else:
            try:
                self.repo[text]
            except (error.RepoError, error.LookupError):
                return
        if path is None:
            path = self.destentry.get_text()
        path = remove_ext(path)
        path = remove_rev(path)
        path = add_rev(path, text)
        path = add_ext(path)
        self.destentry.set_text(path)
        self.prevtarget = text

    def get_selected_archive_type(self):
        """Return a dictionary describing the selected archive type"""
        if self.tarradio.get_active():
            return {'type': 'tar', 'ext': '.tar',
                    'label': _('Tar archives')}
        elif self.tbz2radio.get_active():
            return {'type': 'tbz2', 'ext': '.tar.bz2',
                    'label': _('Bzip2 tar archives')}
        elif self.tgzradio.get_active():
            return {'type': 'tgz', 'ext': '.tar.gz',
                    'label': _('Gzip tar archives')}
        elif self.uzipradio.get_active():
            return {'type': 'uzip', 'ext': '.zip',
                    'label': ('Uncompressed zip archives')}
        elif self.zipradio.get_active():
            return {'type': 'zip', 'ext': '.zip',
                    'label': _('Compressed zip archives')}
        return {'type': 'files', 'ext': None, 'label': None}

    def browse_clicked(self, button):
        """Select the destination directory or file"""
        dest = hglib.fromutf(self.destentry.get_text())
        if not os.path.exists(dest):
            dest = os.path.dirname(dest)
        select = self.get_selected_archive_type()
        if select['type'] == 'files':
            response = gtklib.NativeFolderSelectDialog(
                            initial=dest,
                            title=_('Select Destination Folder')).run()
        else:
            ext = '*' + select['ext']
            label = '%s (%s)' % (select['label'], ext)
            response = gtklib.NativeSaveFileDialogWrapper(
                            initial=dest, 
                            title=_('Select Destination File'),
                            filter=((label, ext),
                                    (_('All Files (*.*)'), '*.*'))).run()
        if response:
            self.destentry.set_text(response)

    def archive(self):
        # verify input
        type = self.get_selected_archive_type()['type']
        dest = self.destentry.get_text()
        if os.path.exists(dest):
            if type != 'files':
                ret = gdialog.Confirm(_('Confirm Overwrite'), [], self,
                            _('The destination "%s" already exists!\n\n'
                              'Do you want to overwrite it?') % dest).run()
                if ret != gtk.RESPONSE_YES:
                    return False
            elif len(os.listdir(dest)) > 0:
                ret = gdialog.Confirm(_('Confirm Overwrite'), [], self,
                            _('The directory "%s" isn\'t empty!\n\n'
                              'Do you want to overwrite it?') % dest).run()
                if ret != gtk.RESPONSE_YES:
                    return False

        # prepare command line
        cmdline = ['hg', 'archive', '--verbose']
        rev = self.combo.get_active_text()
        if rev != WD_PARENT:
            cmdline.append('--rev')
            cmdline.append(rev)
        cmdline.append('-t')
        cmdline.append(type)
        if self.opt_files_in_rev.get_active():
            ctx = self.repo[rev]
            for f in ctx.files():
                cmdline.append('-I')
                cmdline.append(f)
        cmdline.append('--')
        cmdline.append(hglib.fromutf(dest))

        # start archiving
        self.execute_command(cmdline)

def run(ui, *pats, **opts):
    return ArchiveDialog(opts.get('rev'))