/tortoisehg/hgtk/thgstrip.py

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