/tortoisehg/hgtk/update.py

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