PageRenderTime 46ms CodeModel.GetById 17ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgtk/thgimport.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 362 lines | 276 code | 60 blank | 26 comment | 74 complexity | 4e245521791c3656ec90849034a3c64d MD5 | raw file
  1# thgimport.py - TortoiseHg's dialog for (q)importing patches
  2#
  3# Copyright 2009 Yuki KODAMA <endflow.net@gmail.com>
  4#
  5# This software may be used and distributed according to the terms of the
  6# GNU General Public License version 2, incorporated herein by reference.
  7
  8import os
  9import gtk
 10import gobject
 11import tempfile
 12
 13from tortoisehg.util.i18n import _
 14
 15from tortoisehg.hgtk import gtklib, gdialog, cslist
 16
 17COL_NAME  = 0
 18COL_LABEL = 1
 19
 20DEST_REPO = 'repo'
 21DEST_MQ   = 'mq'
 22
 23class ImportDialog(gdialog.GDialog):
 24    """ Dialog to import patches """
 25
 26    def __init__(self, repo=None, dest=DEST_REPO, sources=None):
 27        gdialog.GDialog.__init__(self, resizable=True)
 28
 29        self.done = False
 30        self.mqloaded = hasattr(self.repo, 'mq')
 31        self.clipboard = gtk.Clipboard()
 32        self.tempfiles = []
 33        self.initdest = dest
 34        if not self.mqloaded and dest == DEST_MQ:
 35            self.initdest = DEST_REPO
 36        self.initsrc = sources
 37
 38        # persistent settings
 39        self.recent = self.settings.mrul('src_paths')
 40
 41    ### Start of Overriding Section ###
 42
 43    def get_title(self, reponame):
 44        return _('Import - %s') % reponame
 45
 46    def get_icon(self):
 47        return 'menuimport.ico'
 48
 49    def get_defsize(self):
 50        return (500, 390)
 51
 52    def get_setting_name(self):
 53        return 'import'
 54
 55    def get_body(self, vbox):
 56        # layout table
 57        self.table = table = gtklib.LayoutTable()
 58        vbox.pack_start(table, True, True, 2)
 59
 60        ## source path combo & browse buttons
 61        self.src_list = gtk.ListStore(str)
 62        self.src_combo = gtk.ComboBoxEntry(self.src_list, 0)
 63        self.files_btn = gtk.Button(_('Browse...'))
 64
 65        ## set given sources (but preview later)
 66        if self.initsrc:
 67            src = os.pathsep.join(self.initsrc)
 68            self.src_combo.child.set_text(src)
 69
 70        ## other sources
 71        menubtn = gtk.ToggleButton()
 72        menubtn.set_focus_on_click(False)
 73        menubtn.add(gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE))
 74
 75        m = gtklib.MenuBuilder()
 76        m.append(_('Browse Directory...'), self.dir_clicked,
 77                 gtk.STOCK_DIRECTORY)
 78        m.append(_('Import from Clipboard'), self.clip_clicked,
 79                 gtk.STOCK_PASTE)
 80        self.menu = m.build()
 81
 82        table.add_row(_('Source:'), self.src_combo, 1,
 83                      self.files_btn, menubtn, expand=0)
 84
 85        self.p0check = gtk.CheckButton(_('Do not strip paths '
 86                                         '(-p0), required for SVN patches'))
 87        table.add_row(None, self.p0check, 1, expand=0)
 88
 89        ## add MRU paths to source combo
 90        for path in self.recent:
 91            self.src_list.append([path])
 92
 93        # copy form thgstrip.py
 94        def createlabel():
 95            label = gtk.Label()
 96            label.set_alignment(0, 0.5)
 97            label.set_size_request(-1, 25)
 98            label.size_request()
 99            return label
100
101        ## info label
102        self.infolbl = createlabel()
103        self.infobox = gtk.HBox()
104        self.infobox.pack_start(self.infolbl, False, False)
105        table.add_row(_('Preview:'), self.infobox, padding=False)
106
107        ## dest combo
108        self.dest_model = gtk.ListStore(gobject.TYPE_STRING, # dest name
109                                        gobject.TYPE_STRING) # dest label
110        for row in {DEST_REPO: _('Repository'),
111                    DEST_MQ: _('Patch Queue')}.items():
112            self.dest_model.append(row)
113        self.dest_combo = gtk.ComboBox(self.dest_model)
114        cell = gtk.CellRendererText()
115        self.dest_combo.pack_start(cell, True)
116        self.dest_combo.add_attribute(cell, 'text', COL_LABEL)
117
118        for row in self.dest_model:
119            name, label = self.dest_model[row.path]
120            if name == self.initdest:
121                self.dest_combo.set_active_iter(row.iter)
122                break
123
124        ## patch preview
125        self.cslist = cslist.ChangesetList()
126        table.add_row(None, self.cslist, padding=False,
127                      yopt=gtk.FILL|gtk.EXPAND)
128        self.cslist.set_dnd_enable(True)
129        self.cslist.set_checkbox_enable(True)
130
131        # signal handlers
132        self.files_btn.connect('clicked', self.files_clicked)
133        self.cslist.connect('list-updated', self.list_updated)
134        self.cslist.connect('files-dropped', self.files_dropped)
135        self.src_combo.connect('changed', lambda e: self.preview(queue=True))
136
137        menubtn.connect('button-press-event', self.extra_pressed)
138        popdown = lambda m: menubtn.set_active(False)
139        self.menu.connect('selection-done', popdown)
140        self.menu.connect('cancel', popdown)
141
142        # hook thg-close/thg-exit
143        def hook(dialog, orig):
144            self.unlink_all_tempfiles()
145            return orig(dialog)
146        self.connect('thg-close', hook, gtklib.thgclose)
147        self.connect('thg-exit', hook, gtklib.thgexit)
148
149        # prepare to show
150        self.cslist.clear()
151
152    def get_extras(self, vbox):
153        # dest combo
154        if self.mqloaded:
155            self.dest_combo.show_all()
156            self.dest_combo.hide()
157            self.infobox.pack_start(self.dest_combo, False, False, 6)
158
159        # start preview
160        if self.initsrc:
161            self.preview()
162
163    def get_buttons(self):
164        return [('import', _('Import'), gtk.RESPONSE_OK),
165                ('close', gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)]
166
167    def get_default_button(self):
168        return 'import'
169
170    def get_action_map(self):
171        return {gtk.RESPONSE_OK: self.doimport}
172
173    def switch_to(self, normal, working, cmd):
174        self.table.set_sensitive(normal)
175        self.buttons['import'].set_property('visible', normal)
176        self.buttons['close'].set_property('visible', normal)
177        if normal:
178            self.buttons['close'].grab_focus()
179
180    def command_done(self, returncode, useraborted, *args):
181        self.done = True
182        self.add_to_mru()
183        if returncode == 0:
184            self.cmd.set_result(_('Imported successfully'), style='ok')
185        elif useraborted:
186            self.cmd.set_result(_('Canceled importing'), style='error')
187        else:
188            self.cmd.set_result(_('Failed to import'), style='error')
189
190    def before_close(self):
191        if len(self.cslist.get_items()) != 0 and not self.done:
192            ret = gdialog.Confirm(_('Confirm Close'), [], self,
193                                  _('Do you want to close?')).run()
194            if ret != gtk.RESPONSE_YES:
195                return False # don't close
196            self.unlink_all_tempfiles()
197        return True
198
199    ### End of Overriding Section ###
200
201    def files_clicked(self, button):
202        initdir = self.get_initial_dir()
203        result = gtklib.NativeSaveFileDialogWrapper(title=_('Select Patches'),
204                        initial=initdir, open=True, multi=True).run()
205        if result and result != initdir:
206            if not isinstance(result, basestring):
207                result = os.pathsep.join(result)
208            self.src_combo.child.set_text(result)
209            self.preview()
210
211    def dir_clicked(self, menuitem):
212        initdir = self.get_initial_dir()
213        result = gtklib.NativeFolderSelectDialog(
214                        title=_('Select Directory contains patches:'),
215                        initial=initdir).run()
216        if result and result != initdir:
217            self.src_combo.child.set_text(result)
218            self.preview()
219
220    def clip_clicked(self, menuitem):
221        text = self.clipboard.wait_for_text()
222        if not text:
223            return
224        filepath = self.save_as_tempfile(text)
225        cur_text = self.src_combo.child.get_text()
226        self.src_combo.child.set_text(cur_text + os.pathsep + filepath)
227        self.preview()
228
229    def extra_pressed(self, button, event):
230        if not button.get_active():
231            button.set_active(True)
232            self.menu.show_all()
233            def pos(*args):
234                x, y = button.window.get_origin()
235                r = button.allocation
236                return (x + r.x, y + r.y + r.height, True)
237            self.menu.popup(None, None, pos, event.button, event.time)
238
239    def list_updated(self, cslist, total, sel, *args):
240        self.update_status(sel)
241
242    def files_dropped(self, cslist, files, *args):
243        src = self.src_combo.child.get_text()
244        if src:
245            files = [src] + files
246        self.src_combo.child.set_text(os.pathsep.join(files))
247        self.preview()
248
249    def get_initial_dir(self):
250        src = self.src_combo.child.get_text()
251        if src and os.path.exists(src):
252            if os.path.isdir(src):
253                return src
254            parent = os.path.dirname(src)
255            if parent and os.path.exists(parent):
256                return parent
257        return None
258
259    def add_to_mru(self):
260        dirs = self.get_dirpaths()
261        for dir in dirs:
262            if dir.find(tempfile.gettempdir()) != -1:
263                continue
264            self.recent.add(dir)
265            self.src_list.append([dir])
266        self.settings.write()
267
268    def save_as_tempfile(self, text):
269        fd, filepath = tempfile.mkstemp(prefix='thg-patch-')
270        os.write(fd, text)
271        os.close(fd)
272        self.tempfiles.append(filepath)
273        return filepath
274
275    def unlink_all_tempfiles(self):
276        for path in self.tempfiles:
277            os.unlink(path)
278
279    def update_status(self, count):
280        if count:
281            inner = gtklib.markup(_('%s patches') % count, weight='bold')
282            if self.mqloaded:
283                info = _('%s will be imported to the') % inner
284            else:
285                info = _('%s will be imported to the repository') % inner
286        else:
287            info = gtklib.markup(_('Nothing to import'),
288                                 weight='bold', color=gtklib.DRED)
289        self.infolbl.set_markup(info)
290        if self.mqloaded:
291            self.dest_combo.set_property('visible', bool(count))
292        self.buttons['import'].set_sensitive(bool(count))
293
294    def get_filepaths(self):
295        src = self.src_combo.child.get_text()
296        if not src:
297            return []
298        files = []
299        for path in src.split(os.pathsep):
300            path = path.strip('\r\n\t ')
301            if not os.path.exists(path) or path in files:
302                continue
303            if os.path.isdir(path):
304                entries = os.listdir(path)
305                for entry in entries:
306                    file = os.path.join(path, entry)
307                    if os.path.isfile(file):
308                        files.append(file)
309            elif os.path.isfile(path):
310                files.append(path)
311        return files
312
313    def get_dirpaths(self):
314        dirs = []
315        files = self.get_filepaths()
316        for file in files:
317            dir = os.path.dirname(file)
318            if os.path.isdir(dir) and dir not in dirs:
319                dirs.append(dir)
320        return dirs
321
322    def get_dest(self):
323        iter = self.dest_combo.get_active_iter()
324        return self.dest_model.get(iter, COL_NAME)[0]
325
326    def preview(self, queue=False):
327        files = self.get_filepaths()
328        if files:
329            self.cslist.update(files, self.repo, queue=queue)
330        else:
331            self.cslist.clear()
332
333    def doimport(self):
334        items = self.cslist.get_items(sel=True)
335        files = [file for file, sel in items if sel]
336        if not files:
337            return
338
339        dest = self.get_dest()
340        if DEST_REPO == dest:
341            cmd = 'import'
342        elif DEST_MQ == dest:
343            cmd = 'qimport'
344        else:
345            raise _('unexpected destination name: %s') % dest
346
347        # prepare command line
348        cmdline = ['hg', cmd, '--verbose', '--']
349        cmdline.extend(files)
350        if self.p0check.get_active():
351            cmdline.insert(2, '-p0')
352
353        # start importing
354        self.execute_command(cmdline)
355
356def run(ui, *pats, **opts):
357    dest = opts.get('mq', False) and DEST_MQ or DEST_REPO
358    sources = []
359    for item in pats:
360        if os.path.exists(item):
361            sources.append(os.path.abspath(item))
362    return ImportDialog(dest=dest, sources=sources)