PageRenderTime 36ms CodeModel.GetById 14ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgtk/update.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 294 lines | 219 code | 43 blank | 32 comment | 45 complexity | 604c2bb63b087bed0febd3fc70399962 MD5 | raw file
  1# update.py - TortoiseHg's dialog for updating repo
  2#
  3# Copyright 2007 TK Soh <teekaysoh@gmail.com>
  4# Copyright 2007 Steve Borho <steve@borho.org>
  5#
  6# This software may be used and distributed according to the terms of the
  7# GNU General Public License version 2, incorporated herein by reference.
  8
  9import gtk
 10
 11from mercurial import ui, error
 12
 13from tortoisehg.util.i18n import _
 14from tortoisehg.util import hglib
 15
 16from tortoisehg.hgtk import csinfo, gtklib, gdialog
 17
 18class UpdateDialog(gdialog.GDialog):
 19    """ Dialog to update Mercurial repo """
 20    def __init__(self, rev=None):
 21        gdialog.GDialog.__init__(self)
 22        self.rev = rev
 23
 24    ### Start of Overriding Section ###
 25
 26    def get_title(self, reponame):
 27        return _('Update - %s') % reponame
 28
 29    def get_icon(self):
 30        return 'menucheckout.ico'
 31
 32    def get_setting_name(self):
 33        return 'update'
 34
 35    def get_body(self, vbox):
 36        # layout table
 37        table = gtklib.LayoutTable()
 38        vbox.pack_start(table, False, False, 2)
 39        self.table = table
 40
 41        ## revision label & combobox
 42        self.revcombo = combo = gtk.combo_box_entry_new_text()
 43        entry = combo.child
 44        entry.set_width_chars(38)
 45        entry.connect('activate', lambda b: self.response(gtk.RESPONSE_OK))
 46        table.add_row(_('Update to:'), combo, padding=False)
 47
 48        ## fill list of combo
 49        if self.rev != None:
 50            combo.append_text(str(self.rev))
 51        else:
 52            combo.append_text(self.repo.dirstate.branch())
 53        combo.set_active(0)
 54        for name in hglib.getlivebranch(self.repo):
 55            combo.append_text(name)
 56
 57        tags = list(self.repo.tags())
 58        tags.sort()
 59        tags.reverse()
 60        for tag in tags:
 61            combo.append_text(hglib.toutf(tag))
 62
 63        ## changeset summaries
 64        style = csinfo.labelstyle(contents=('%(rev)s', ' %(branch)s',
 65                       ' %(tags)s', '\n%(summary)s'), selectable=True, width=350)
 66        factory = csinfo.factory(self.repo, style=style)
 67
 68        def add_with_pad(title, cslabel):
 69            label = gtk.Label(title)
 70            label.set_alignment(1, 0)
 71            headbox = gtk.VBox()
 72            headbox.pack_start(label, False, False, 2)
 73            headbox.pack_start(gtk.VBox())
 74            table.add_row(headbox, cslabel, yhopt=gtk.FILL|gtk.EXPAND)
 75
 76        ## summary of target revision
 77        self.target_label = factory()
 78        add_with_pad(_('Target:'), self.target_label)
 79
 80        ## summary of parent 1 revision
 81        self.parent1_label = factory()
 82
 83        ## summary of parent 2 revision if needs
 84        self.ctxs = self.repo[None].parents()
 85        if len(self.ctxs) == 2:
 86            add_with_pad(_('Parent 1:'), self.parent1_label)
 87            self.parent2_label = factory()
 88            add_with_pad(_('Parent 2:'), self.parent2_label)
 89        else:
 90            add_with_pad(_('Parent:'), self.parent1_label)
 91            self.parent2_label = None
 92
 93        ## option expander
 94        self.expander = gtk.Expander(_('Options:'))
 95        self.expander.connect('notify::expanded', self.options_expanded)
 96
 97        ### update method (fixed)
 98        self.opt_clean = gtk.CheckButton(_('Discard local changes, '
 99                                           'no backup (-C/--clean)'))
100        table.add_row(self.expander, self.opt_clean)
101
102        ### other options (foldable), put later
103        ### automatically merge, if possible (similar to command-line behavior)
104        self.opt_merge = gtk.CheckButton(_('Always merge (when possible)'))
105
106        ### always show command log widget
107        self.opt_showlog = gtk.CheckButton(_('Always show log'))
108
109        # signal handlers
110        self.revcombo.connect('changed', lambda b: self.update_summaries())
111        self.opt_clean.connect('toggled', lambda b: self.update_summaries())
112
113        # prepare to show
114        self.update_summaries()
115
116    def get_extras(self, vbox):
117        # append options
118        self.opttable = gtklib.LayoutTable()
119        vbox.pack_start(self.opttable, False, False)
120        self.opttable.add_row(None, self.opt_merge, ypad=0)
121        self.opttable.add_row(None, self.opt_showlog, ypad=0)
122
123        # layout group
124        layout = gtklib.LayoutGroup()
125        layout.add(self.table, self.opttable, force=True)
126
127    def get_buttons(self):
128        return [('update', _('Update'), gtk.RESPONSE_OK),
129                ('close', gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)]
130
131    def get_default_button(self):
132        return 'update'
133
134    def get_action_map(self):
135        return {gtk.RESPONSE_OK: self.update}
136
137    def switch_to(self, normal, working, cmd):
138        self.table.set_sensitive(normal)
139        self.opttable.set_sensitive(normal)
140        self.buttons['update'].set_property('visible', normal)
141        self.buttons['close'].set_property('visible', normal)
142        if normal:
143            self.buttons['close'].grab_focus()
144        if working:
145            self.set_resizable(True)
146            self.vbox.set_child_packing(self.cmd, True, True, 6, gtk.PACK_START)
147        if cmd and self.opt_showlog.get_active():
148            self.cmd.show_log()
149
150    def command_done(self, returncode, useraborted, *args):
151        if returncode == 0:
152            self.cmd.set_result(_('Updated successfully'), style='ok')
153        elif useraborted:
154            self.cmd.set_result(_('Canceled updating'), style='error')
155        else:
156            self.cmd.set_result(_('Failed to update'), style='error')
157
158    def load_settings(self):
159        merge = self.settings.get_value('mergedefault', False)
160        showlog = self.settings.get_value('showlog', False)
161        self.opt_merge.set_active(merge)
162        self.opt_showlog.set_active(showlog)
163
164    def store_settings(self):
165        checked = self.opt_merge.get_active()
166        showlog = self.opt_showlog.get_active()
167        self.settings.set_value('mergedefault', checked)
168        self.settings.set_value('showlog', showlog)
169        self.settings.write()
170
171    ### End of Overriding Section ###
172
173    def options_expanded(self, expander, *args):
174        if expander.get_expanded():
175            self.opttable.show_all()
176        else:
177            self.opttable.hide()
178
179    def update_summaries(self):
180        ctxs = self.ctxs
181        self.parent1_label.update(ctxs[0])
182        merge = len(ctxs) == 2
183        if merge:
184            self.parent2_label.update(ctxs[1])
185        newrev = hglib.fromutf(self.revcombo.get_active_text())
186        try:
187            new_ctx = self.repo[newrev]
188            if not merge and new_ctx.rev() == ctxs[0].rev():
189                self.target_label.set_label(_('(same as parent)'))
190                clean = self.opt_clean.get_active()
191                self.buttons['update'].set_sensitive(clean)
192            else:
193                self.target_label.update(self.repo[newrev])
194                self.buttons['update'].set_sensitive(True)
195        except (error.LookupError, error.RepoLookupError, error.RepoError):
196            self.target_label.set_label(_('unknown revision!'))
197            self.buttons['update'].set_sensitive(False)
198
199    def update(self):
200        cmdline = ['hg', 'update', '--verbose']
201        rev = hglib.fromutf(self.revcombo.get_active_text())
202        cmdline.append('--rev')
203        cmdline.append(rev)
204
205        if self.opt_clean.get_active():
206            cmdline.append('--clean')
207        else:
208            cur = self.repo['.']
209            node = self.repo[rev]
210            def isclean():
211                '''whether WD is changed'''
212                wc = self.repo[None]
213                return not (wc.modified() or wc.added() or wc.removed())
214            def ismergedchange():
215                '''whether the local changes are merged (have 2 parents)'''
216                wc = self.repo[None]
217                return len(wc.parents()) == 2
218            def iscrossbranch(p1, p2):
219                '''whether p1 -> p2 crosses branch'''
220                pa = p1.ancestor(p2)
221                return p1.branch() != p2.branch() or (p1 != pa and p2 != pa)
222            def islocalmerge(p1, p2, clean=None):
223                if clean is None:
224                    clean = isclean()
225                pa = p1.ancestor(p2)
226                return not clean and (p1 == pa or p2 == pa)
227            def confirmupdate(clean=None):
228                if clean is None:
229                    clean = isclean()
230
231                msg = _('Detected uncommitted local changes in working tree.\n'
232                        'Please select to continue:\n\n')
233                data = {'discard': (_('&Discard'),
234                                    _('Discard - discard local changes, no backup')),
235                        'shelve': (_('&Shelve'),
236                                   _('Shelve - launch Shelve tool and continue')),
237                        'merge': (_('&Merge'),
238                                  _('Merge - allow to merge with local changes')),
239                        'cancel': (_('&Cancel'), None)}
240
241                opts = [data['discard']]
242                if not ismergedchange():
243                    opts.append(data['shelve'])
244                if islocalmerge(cur, node, clean):
245                    opts.append(data['merge'])
246                opts.append(data['cancel'])
247
248                msg += '\n'.join([ desc for label, desc in opts if desc ])
249                buttons = [ label for label, desc in opts ]
250                cancel = len(opts) - 1
251                retcode = gdialog.CustomPrompt(_('Confirm Update'), msg, self,
252                                    buttons, default=cancel, esc=cancel).run()
253                retlabel = buttons[retcode]
254                retid = [ id for id, (label, desc) in data.items() \
255                             if label == retlabel ][0]
256                return dict([(id, id == retid) for id in data.keys()])
257            # If merge-by-default, we want to merge whenever possible,
258            # without prompting user (similar to command-line behavior)
259            defaultmerge = self.opt_merge.get_active()
260            clean = isclean()
261            if clean:
262                cmdline.append('--check')
263            elif not (defaultmerge and islocalmerge(cur, node, clean)):
264                ret = confirmupdate(clean)
265                if ret['discard']:
266                    cmdline.append('--clean')
267                elif ret['shelve']:
268                    def launch_shelve():
269                        from tortoisehg.hgtk import thgshelve
270                        dlg = thgshelve.run(ui.ui())
271                        dlg.set_transient_for(self)
272                        dlg.set_modal(True)
273                        dlg.display()
274                        dlg.connect('destroy', lambda w: self.update())
275                    gtklib.idle_add_single_call(launch_shelve)
276                    return # retry later, no need to destroy
277                elif ret['merge']:
278                    pass # no args
279                elif ret['cancel']:
280                    self.cmd.log.append(_('[canceled by user]\n'), error=True)
281                    self.do_switch_to(gdialog.MODE_WORKING)
282                    self.abort()
283                    return
284                else:
285                    raise _('invalid dialog result: %s') % ret
286
287        # start updating
288        self.execute_command(cmdline)
289
290def run(ui, *pats, **opts):
291    rev = None
292    if opts.get('rev'):
293        rev = opts.get('rev')[0]
294    return UpdateDialog(rev)