PageRenderTime 32ms CodeModel.GetById 14ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgtk/thgstrip.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 262 lines | 190 code | 42 blank | 30 comment | 31 complexity | 6c7ada5f69c2c1daaa1037315ae474e9 MD5 | raw file
  1# thgstrip.py - strip dialog for TortoiseHg
  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 re
  9import os
 10import gtk
 11
 12from mercurial import error
 13
 14from tortoisehg.util.i18n import _
 15from tortoisehg.util import hglib
 16
 17from tortoisehg.hgtk import gtklib, gdialog, cslist
 18
 19class StripDialog(gdialog.GDialog):
 20    """ Dialog to strip changesets """
 21
 22    def __init__(self, rev=None, graphview=None, *pats):
 23        gdialog.GDialog.__init__(self, resizable=True)
 24        self.set_after_done(False)
 25
 26        if len(pats) > 0:
 27            rev = pats[0]
 28        elif rev is None:
 29            rev = 'tip'
 30        self.initrev = str(rev)
 31        self.graphview = graphview
 32
 33    ### Start of Overriding Section ###
 34
 35    def get_title(self, reponame):
 36        return _('Strip - %s') % reponame
 37
 38    def get_icon(self):
 39        return 'menudelete.ico'
 40
 41    def get_defsize(self):
 42        return (500, 380)
 43
 44    def get_body(self, vbox):
 45        # layout table
 46        self.table = table = gtklib.LayoutTable()
 47        vbox.pack_start(table, True, True, 2)
 48
 49        ## target revision combo
 50        self.revcombo = gtk.combo_box_entry_new_text()
 51        table.add_row(_('Strip:'), self.revcombo)
 52        reventry = self.revcombo.child
 53        reventry.set_width_chars(32)
 54
 55        ### fill combo list
 56        self.revcombo.append_text(self.initrev)
 57        self.revcombo.set_active(0)
 58        for name in hglib.getlivebranch(self.repo):
 59            self.revcombo.append_text(name)
 60
 61        tags = list(self.repo.tags())
 62        tags.sort()
 63        tags.reverse()
 64        for tag in tags:
 65            self.revcombo.append_text(hglib.toutf(tag))
 66
 67        def createlabel():
 68            label = gtk.Label()
 69            label.set_alignment(0, 0.5)
 70            label.set_size_request(-1, 24)
 71            label.size_request()
 72            return label
 73
 74        ## result label
 75        self.resultlbl = createlabel()
 76        table.add_row(_('Preview:'), self.resultlbl, padding=False)
 77
 78        ## changeset list
 79        self.cslist = cslist.ChangesetList()
 80        self.cslist.set_activatable_enable(True)
 81        table.add_row(None, self.cslist, padding=False,
 82                      yopt=gtk.FILL|gtk.EXPAND)
 83
 84        ## options
 85        self.expander = gtk.Expander(_('Options:'))
 86        self.expander.connect('notify::expanded', self.options_expanded)
 87
 88        ### force option (fixed)
 89        self.forceopt = gtk.CheckButton(_('Discard local changes, no backup'
 90                                          ' (-f/--force)'))
 91        table.add_row(self.expander, self.forceopt)
 92
 93        # signal handlers
 94        reventry.connect('activate', lambda b: self.response(gtk.RESPONSE_OK))
 95        self.revcombo.connect('changed', lambda c: self.preview(queue=True))
 96        self.cslist.connect('list-updated', self.preview_updated)
 97        self.cslist.connect('item-activated', self.item_activated)
 98
 99        # prepare to show
100        self.preview()
101
102    def get_extras(self, vbox):
103        # backup types (foldable)
104        self.butable = gtklib.LayoutTable()
105        vbox.pack_start(self.butable, False, False)
106        def add_type(desc):
107            group = hasattr(self, 'buopt_all') and self.buopt_all or None
108            radio = gtk.RadioButton(group, desc)
109            self.butable.add_row(None, radio, ypad=0)
110            return radio
111        self.buopt_all = add_type(_('Backup all (default)'))
112        self.buopt_part = add_type(_('Backup unrelated changesets'
113                                     ' (-b/--backup)'))
114        self.buopt_none = add_type(_('No backup (-n/--nobackup)'))
115
116        # layout group
117        layout = gtklib.LayoutGroup()
118        layout.add(self.table, self.butable, force=True)
119
120    def get_buttons(self):
121        return [('strip', _('Strip'), gtk.RESPONSE_OK),
122                ('close', gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)]
123
124    def get_default_button(self):
125        return 'strip'
126
127    def get_action_map(self):
128        return {gtk.RESPONSE_OK: self.strip}
129
130    def switch_to(self, normal, working, cmd):
131        self.table.set_sensitive(normal)
132        self.butable.set_sensitive(normal)
133        self.buttons['strip'].set_property('visible', normal)
134        self.buttons['close'].set_property('visible', normal)
135        if normal:
136            self.buttons['close'].grab_focus()
137
138    def command_done(self, returncode, useraborted, *args):
139        if returncode == 0:
140            self.cmd.set_result(_('Stripped successfully'), style='ok')
141            self.after_strip()
142        elif useraborted:
143            self.cmd.set_result(_('Canceled stripping'), style='error')
144        else:
145            self.cmd.set_result(_('Failed to strip'), style='error')
146
147    ### End of Overriding Section ###
148
149    def preview(self, limit=True, queue=False, force=False):
150        # check revision
151        rev = self.get_rev()
152        if rev is None:
153            self.cslist.clear()
154            return
155
156        # enumerate all descendants
157        # borrowed from strip() in 'mercurial/repair.py'
158        cl = self.repo.changelog
159        tostrip = [rev,]
160        for r in xrange(rev + 1, len(cl)):
161            parents = cl.parentrevs(r)
162            if parents[0] in tostrip or parents[1] in tostrip:
163                tostrip.append(r)
164
165        # update preview
166        self.cslist.update(tostrip, self.repo, limit, queue)
167
168    def options_expanded(self, expander, *args):
169        if expander.get_expanded():
170            self.butable.show_all()
171        else:
172            self.butable.hide()
173
174    def preview_updated(self, cslist, total, *args):
175        if total is None:
176            info = gtklib.markup(_('Unknown revision!'),
177                                 weight='bold', color=gtklib.DRED)
178        else:
179            inner = gtklib.markup(_('%s changesets') % total, weight='bold')
180            info = _('%s will be stripped') % inner
181        self.resultlbl.set_markup(info)
182        self.buttons['strip'].set_sensitive(bool(total))
183
184    def item_activated(self, cslist, rev, *args):
185        if self.graphview:
186            self.graphview.set_revision_id(int(rev))
187
188    def get_rev(self):
189        """ Return integer revision number or None """
190        revstr = self.revcombo.get_active_text()
191        if revstr is None or len(revstr) == 0:
192            return None
193        if isinstance(revstr, basestring):
194            revstr = hglib.fromutf(revstr)
195        try:
196            revnum = self.repo[revstr].rev()
197        except (error.RepoError, error.LookupError):
198            return None
199        return revnum
200
201    def strip(self):
202        def isclean():
203            '''whether WD is changed'''
204            wc = self.repo[None]
205            return not (wc.modified() or wc.added() or wc.removed())
206        revstr = self.revcombo.get_active_text()
207        cmdline = ['hg', 'strip', '--verbose', revstr]
208
209        # local changes
210        if self.forceopt.get_active():
211            cmdline.append('--force')
212        else:
213            if not isclean():
214                ret = gdialog.CustomPrompt(_('Confirm Strip'),
215                              _('Detected uncommitted local changes.\nDo'
216                                ' you want to discard them and continue?'),
217                              self, (_('&Yes (--force)'), _('&No')),
218                              default=1, esc=1).run()
219                if ret == 0:
220                    cmdline.append('--force')
221                else:
222                    return
223
224        # backup options
225        if self.buopt_part.get_active():
226            cmdline.append('--backup')
227        elif self.buopt_none.get_active():
228            cmdline.append('--nobackup')
229
230        # start strip
231        self.execute_command(cmdline)
232
233    def after_strip(self):
234        if self.buopt_none.get_active():
235            self.response(gtk.RESPONSE_CLOSE)
236            return
237        # clear changeset list
238        self.revcombo.child.set_text('')
239        # show backup dir
240        root = self.repo.root
241        bakdir = os.path.join(root, r'.hg\strip-backup')
242        escaped = bakdir.replace('\\', '\\\\')
243        buf = self.cmd.log.buffer
244        text = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
245        m = re.search(escaped + r'\\[0-9abcdef]{12}-backup', text, re.I)
246        if m:
247            def open_bakdir():
248                gtklib.NativeFileManager(bakdir).run()
249            # backup bundle label & button
250            self.bubox = gtk.HBox()
251            self.vbox.pack_start(self.bubox, False, False, 2)
252            self.bulabel = gtk.Label(_('Saved at: %s') % m.group(0))
253            self.bubox.pack_start(self.bulabel, True, True, 8)
254            self.bulabel.set_alignment(0, 0.5)
255            self.bulabel.set_selectable(True)
256            self.bubtn = gtk.Button(_('Open...'))
257            self.bubox.pack_start(self.bubtn, False, False, 2)
258            self.bubtn.connect('clicked', lambda b: open_bakdir())
259            self.bubox.show_all()
260
261def run(ui, *pats, **opts):
262    return StripDialog(None, *pats)