/tortoisehg/hgqt/run.py

https://bitbucket.org/tortoisehg/hgtk/ · Python · 1059 lines · 948 code · 66 blank · 45 comment · 127 complexity · 8ceba6cfe48d121725675d8919c61c7e MD5 · raw file

  1. # run.py - front-end script for TortoiseHg dialogs
  2. #
  3. # Copyright 2008 Steve Borho <steve@borho.org>
  4. # Copyright 2008 TK Soh <teekaysoh@gmail.com>
  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. shortlicense = '''
  9. Copyright (C) 2008-2010 Steve Borho <steve@borho.org> and others.
  10. This is free software; see the source for copying conditions. There is NO
  11. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. '''
  13. import os
  14. import pdb
  15. import sys
  16. import subprocess
  17. import traceback
  18. from PyQt4.QtCore import *
  19. from PyQt4.QtGui import *
  20. import mercurial.ui as _ui
  21. from mercurial import hg, util, fancyopts, cmdutil, extensions, error
  22. from tortoisehg.hgqt.i18n import agettext as _
  23. from tortoisehg.util import hglib, paths, shlib, i18n
  24. from tortoisehg.util import version as thgversion
  25. from tortoisehg.hgqt import qtlib
  26. from tortoisehg.hgqt.bugreport import run as bugrun
  27. try:
  28. from tortoisehg.util.config import nofork as config_nofork
  29. except ImportError:
  30. config_nofork = None
  31. try:
  32. import win32con
  33. openflags = win32con.CREATE_NO_WINDOW
  34. except ImportError:
  35. openflags = 0
  36. nonrepo_commands = '''userconfig shellconfig clone debugcomplete init
  37. about help version thgstatus serve rejects log'''
  38. def dispatch(args):
  39. """run the command specified in args"""
  40. try:
  41. u = _ui.ui()
  42. if '--traceback' in args:
  43. u.setconfig('ui', 'traceback', 'on')
  44. if '--debugger' in args:
  45. pdb.set_trace()
  46. return _runcatch(u, args)
  47. except SystemExit:
  48. pass
  49. except KeyboardInterrupt:
  50. print _('\nCaught keyboard interrupt, aborting.\n')
  51. except:
  52. if '--debugger' in args:
  53. pdb.post_mortem(sys.exc_info()[2])
  54. error = traceback.format_exc()
  55. opts = {}
  56. opts['cmd'] = ' '.join(sys.argv[1:])
  57. opts['error'] = error
  58. opts['nofork'] = True
  59. return qtrun(bugrun, u, **opts)
  60. origwdir = os.getcwd()
  61. def portable_fork(ui, opts):
  62. if 'THG_GUI_SPAWN' in os.environ or (
  63. not opts.get('fork') and opts.get('nofork')):
  64. os.environ['THG_GUI_SPAWN'] = '1'
  65. return
  66. elif ui.configbool('tortoisehg', 'guifork', None) is not None:
  67. if not ui.configbool('tortoisehg', 'guifork'):
  68. return
  69. elif config_nofork:
  70. return
  71. os.environ['THG_GUI_SPAWN'] = '1'
  72. # Spawn background process and exit
  73. if hasattr(sys, "frozen"):
  74. args = sys.argv
  75. else:
  76. args = [sys.executable] + sys.argv
  77. cmdline = subprocess.list2cmdline(args)
  78. os.chdir(origwdir)
  79. subprocess.Popen(cmdline,
  80. creationflags=openflags,
  81. shell=True)
  82. sys.exit(0)
  83. # Windows and Nautilus shellext execute
  84. # "thg subcmd --listfile TMPFILE" or "thg subcmd --listfileutf8 TMPFILE"(planning) .
  85. # Extensions written in .hg/hgrc is enabled after calling
  86. # extensions.loadall(lui)
  87. #
  88. # 1. win32mbcs extension
  89. # Japanese shift_jis and Chinese big5 include '0x5c'(backslash) in filename.
  90. # Mercurial resolves this problem with win32mbcs extension.
  91. # So, thg must parse path after loading win32mbcs extension.
  92. #
  93. # 2. fixutf8 extension
  94. # fixutf8 extension requires paths encoding utf-8.
  95. # So, thg need to convert to utf-8.
  96. #
  97. _lines = []
  98. _linesutf8 = []
  99. def get_lines_from_listfile(filename, isutf8):
  100. global _lines
  101. global _linesutf8
  102. try:
  103. if filename == '-':
  104. lines = [ x.replace("\n", "") for x in sys.stdin.readlines() ]
  105. else:
  106. fd = open(filename, "r")
  107. lines = [ x.replace("\n", "") for x in fd.readlines() ]
  108. fd.close()
  109. os.unlink(filename)
  110. if isutf8:
  111. _linesutf8 = lines
  112. else:
  113. _lines = lines
  114. except IOError, e:
  115. sys.stderr.write(_('can not read file "%s". Ignored.\n') % filename)
  116. def get_files_from_listfile():
  117. global _lines
  118. global _linesutf8
  119. lines = []
  120. need_to_utf8 = False
  121. if os.name == 'nt':
  122. try:
  123. fixutf8 = extensions.find("fixutf8")
  124. if fixutf8:
  125. need_to_utf8 = True
  126. except KeyError:
  127. pass
  128. if need_to_utf8:
  129. lines += _linesutf8
  130. for l in _lines:
  131. lines.append(hglib.toutf(l))
  132. else:
  133. lines += _lines
  134. for l in _linesutf8:
  135. lines.append(hglib.fromutf(l))
  136. # Convert absolute file paths to repo/cwd canonical
  137. cwd = os.getcwd()
  138. root = paths.find_root(cwd)
  139. if not root:
  140. return lines
  141. if cwd == root:
  142. cwd_rel = ''
  143. else:
  144. cwd_rel = cwd[len(root+os.sep):] + os.sep
  145. files = []
  146. for f in lines:
  147. try:
  148. cpath = util.canonpath(root, cwd, f)
  149. # canonpath will abort on .hg/ paths
  150. except util.Abort:
  151. continue
  152. if cpath.startswith(cwd_rel):
  153. cpath = cpath[len(cwd_rel):]
  154. files.append(cpath)
  155. else:
  156. files.append(f)
  157. return files
  158. def _parse(ui, args):
  159. options = {}
  160. cmdoptions = {}
  161. try:
  162. args = fancyopts.fancyopts(args, globalopts, options)
  163. except fancyopts.getopt.GetoptError, inst:
  164. raise error.ParseError(None, inst)
  165. if args:
  166. alias, args = args[0], args[1:]
  167. elif options['help']:
  168. help_(ui, None)
  169. sys.exit()
  170. else:
  171. alias, args = 'workbench', []
  172. aliases, i = cmdutil.findcmd(alias, table, ui.config("ui", "strict"))
  173. for a in aliases:
  174. if a.startswith(alias):
  175. alias = a
  176. break
  177. cmd = aliases[0]
  178. c = list(i[1])
  179. # combine global options into local
  180. for o in globalopts:
  181. c.append((o[0], o[1], options[o[1]], o[3]))
  182. try:
  183. args = fancyopts.fancyopts(args, c, cmdoptions)
  184. except fancyopts.getopt.GetoptError, inst:
  185. raise error.ParseError(cmd, inst)
  186. # separate global options back out
  187. for o in globalopts:
  188. n = o[1]
  189. options[n] = cmdoptions[n]
  190. del cmdoptions[n]
  191. listfile = options.get('listfile')
  192. if listfile:
  193. del options['listfile']
  194. get_lines_from_listfile(listfile, False)
  195. listfileutf8 = options.get('listfileutf8')
  196. if listfileutf8:
  197. del options['listfileutf8']
  198. get_lines_from_listfile(listfileutf8, True)
  199. return (cmd, cmd and i[0] or None, args, options, cmdoptions, alias)
  200. def _runcatch(ui, args):
  201. try:
  202. try:
  203. return runcommand(ui, args)
  204. finally:
  205. ui.flush()
  206. except error.ParseError, inst:
  207. if inst.args[0]:
  208. ui.status(_("thg %s: %s\n") % (inst.args[0], inst.args[1]))
  209. help_(ui, inst.args[0])
  210. else:
  211. ui.status(_("thg: %s\n") % inst.args[1])
  212. help_(ui, 'shortlist')
  213. except error.AmbiguousCommand, inst:
  214. ui.status(_("thg: command '%s' is ambiguous:\n %s\n") %
  215. (inst.args[0], " ".join(inst.args[1])))
  216. except error.UnknownCommand, inst:
  217. ui.status(_("thg: unknown command '%s'\n") % inst.args[0])
  218. help_(ui, 'shortlist')
  219. except error.RepoError, inst:
  220. ui.status(_("abort: %s!\n") % inst)
  221. return -1
  222. def runcommand(ui, args):
  223. cmd, func, args, options, cmdoptions, alias = _parse(ui, args)
  224. cmdoptions['alias'] = alias
  225. ui.setconfig("ui", "verbose", str(bool(options["verbose"])))
  226. i18n.setlanguage(ui.config('tortoisehg', 'ui.language'))
  227. if options['help']:
  228. return help_(ui, cmd)
  229. path = options['repository']
  230. if path:
  231. if path.startswith('bundle:'):
  232. s = path[7:].split('+', 1)
  233. if len(s) == 1:
  234. path, bundle = os.getcwd(), s[0]
  235. else:
  236. path, bundle = s
  237. cmdoptions['bundle'] = os.path.abspath(bundle)
  238. path = ui.expandpath(path)
  239. cmdoptions['repository'] = path
  240. os.chdir(path)
  241. if options['fork']:
  242. cmdoptions['fork'] = True
  243. if options['nofork'] or options['profile']:
  244. cmdoptions['nofork'] = True
  245. path = paths.find_root(os.getcwd())
  246. if path:
  247. try:
  248. lui = ui.copy()
  249. lui.readconfig(os.path.join(path, ".hg", "hgrc"))
  250. except IOError:
  251. pass
  252. else:
  253. lui = ui
  254. hglib.wrapextensionsloader() # enable blacklist of extensions
  255. extensions.loadall(lui)
  256. args += get_files_from_listfile()
  257. if options['quiet']:
  258. ui.quiet = True
  259. if cmd not in nonrepo_commands.split() and not path:
  260. raise error.RepoError(_("There is no Mercurial repository here"
  261. " (.hg not found)"))
  262. cmdoptions['mainapp'] = True
  263. d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
  264. return _runcommand(lui, options, cmd, d)
  265. def _runcommand(ui, options, cmd, cmdfunc):
  266. def checkargs():
  267. try:
  268. return cmdfunc()
  269. except error.SignatureError:
  270. raise error.ParseError(cmd, _("invalid arguments"))
  271. if options['profile']:
  272. format = ui.config('profiling', 'format', default='text')
  273. if not format in ['text', 'kcachegrind']:
  274. ui.warn(_("unrecognized profiling format '%s'"
  275. " - Ignored\n") % format)
  276. format = 'text'
  277. output = ui.config('profiling', 'output')
  278. if output:
  279. path = ui.expandpath(output)
  280. ostream = open(path, 'wb')
  281. else:
  282. ostream = sys.stderr
  283. try:
  284. from mercurial import lsprof
  285. except ImportError:
  286. raise util.Abort(_(
  287. 'lsprof not available - install from '
  288. 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
  289. p = lsprof.Profiler()
  290. p.enable(subcalls=True)
  291. try:
  292. return checkargs()
  293. finally:
  294. p.disable()
  295. if format == 'kcachegrind':
  296. import lsprofcalltree
  297. calltree = lsprofcalltree.KCacheGrind(p)
  298. calltree.output(ostream)
  299. else:
  300. # format == 'text'
  301. stats = lsprof.Stats(p.getstats())
  302. stats.sort()
  303. stats.pprint(top=10, file=ostream, climit=5)
  304. if output:
  305. ostream.close()
  306. else:
  307. return checkargs()
  308. class _QtRunner(QObject):
  309. """Run Qt app and hold its windows
  310. NOTE: This object will be instantiated before QApplication, it means
  311. there's a limitation on Qt's event handling. See
  312. http://doc.qt.nokia.com/4.6/threads-qobject.html#per-thread-event-loop
  313. """
  314. _exceptionOccured = pyqtSignal(object, object, object)
  315. # {exception class: message}
  316. # It doesn't check the hierarchy of exception classes for simplicity.
  317. _recoverableexc = {
  318. error.RepoLookupError: _('Try refreshing your repository.'),
  319. error.ParseError: _('Error string "%(arg0)s" at %(arg1)s<br>Please '
  320. '<a href="#edit:%(arg1)s">edit</a> your config'),
  321. }
  322. def __init__(self):
  323. super(_QtRunner, self).__init__()
  324. self._mainapp = None
  325. self._dialogs = []
  326. self.errors = []
  327. sys.excepthook = lambda t, v, o: self.ehook(t, v, o)
  328. # can be emitted by another thread; postpones it until next
  329. # eventloop of main (GUI) thread.
  330. self._exceptionOccured.connect(self.putexception,
  331. Qt.QueuedConnection)
  332. def ehook(self, etype, evalue, tracebackobj):
  333. 'Will be called by any thread, on any unhandled exception'
  334. elist = traceback.format_exception(etype, evalue, tracebackobj)
  335. if 'THGDEBUG' in os.environ:
  336. sys.stderr.write(''.join(elist))
  337. self._exceptionOccured.emit(etype, evalue, tracebackobj)
  338. # not thread-safe to touch self.errors here
  339. @pyqtSlot(object, object, object)
  340. def putexception(self, etype, evalue, tracebackobj):
  341. 'Enque exception info and display it later; run in main thread'
  342. if not self.errors:
  343. QTimer.singleShot(10, self.excepthandler)
  344. self.errors.append((etype, evalue, tracebackobj))
  345. @pyqtSlot()
  346. def excepthandler(self):
  347. 'Display exception info; run in main (GUI) thread'
  348. try:
  349. self._showexceptiondialog()
  350. except:
  351. # make sure to quit mainloop first, so that it never leave
  352. # zombie process.
  353. self._mainapp.exit(1)
  354. self._printexception()
  355. finally:
  356. self.errors = []
  357. def _showexceptiondialog(self):
  358. from tortoisehg.hgqt.bugreport import BugReport, ExceptionMsgBox
  359. opts = {}
  360. opts['cmd'] = ' '.join(sys.argv[1:])
  361. opts['error'] = ''.join(''.join(traceback.format_exception(*args))
  362. for args in self.errors)
  363. etype, evalue = self.errors[0][:2]
  364. if len(self.errors) == 1 and etype in self._recoverableexc:
  365. opts['values'] = evalue
  366. dlg = ExceptionMsgBox(hglib.tounicode(str(evalue)),
  367. self._recoverableexc[etype], opts,
  368. parent=self._mainapp.activeWindow())
  369. elif etype is KeyboardInterrupt:
  370. if qtlib.QuestionMsgBox(_('Keyboard interrupt'),
  371. _('Close this application?')):
  372. QApplication.quit()
  373. else:
  374. self.errors = []
  375. return
  376. else:
  377. dlg = BugReport(opts, parent=self._mainapp.activeWindow())
  378. dlg.exec_()
  379. def _printexception(self):
  380. for args in self.errors:
  381. traceback.print_exception(*args)
  382. def __call__(self, dlgfunc, ui, *args, **opts):
  383. portable_fork(ui, opts)
  384. if self._mainapp:
  385. self._opendialog(dlgfunc, ui, *args, **opts)
  386. return
  387. QSettings.setDefaultFormat(QSettings.IniFormat)
  388. self._mainapp = QApplication(sys.argv)
  389. try:
  390. # default org is used by QSettings
  391. self._mainapp.setApplicationName('TortoiseHgQt')
  392. self._mainapp.setOrganizationName('TortoiseHg')
  393. self._mainapp.setOrganizationDomain('tortoisehg.org')
  394. self._mainapp.setApplicationVersion(thgversion.version())
  395. qtlib.setup_font_substitutions()
  396. qtlib.configstyles(ui)
  397. qtlib.initfontcache(ui)
  398. self._mainapp.setWindowIcon(qtlib.geticon('thg_logo'))
  399. dlg = dlgfunc(ui, *args, **opts)
  400. if dlg:
  401. dlg.show()
  402. dlg.raise_()
  403. except:
  404. # Exception before starting eventloop needs to be postponed;
  405. # otherwise it will be ignored silently.
  406. def reraise():
  407. raise
  408. QTimer.singleShot(0, reraise)
  409. try:
  410. return self._mainapp.exec_()
  411. finally:
  412. self._mainapp = None
  413. def _opendialog(self, dlgfunc, ui, *args, **opts):
  414. dlg = dlgfunc(ui, *args, **opts)
  415. if not dlg:
  416. return
  417. self._dialogs.append(dlg) # avoid garbage collection
  418. if hasattr(dlg, 'finished') and hasattr(dlg.finished, 'connect'):
  419. dlg.finished.connect(dlg.deleteLater)
  420. # NOTE: Somehow `destroyed` signal doesn't emit the original obj.
  421. # So we cannot write `dlg.destroyed.connect(self._forgetdialog)`.
  422. dlg.destroyed.connect(lambda: self._forgetdialog(dlg))
  423. dlg.show()
  424. def _forgetdialog(self, dlg):
  425. """forget the dialog to be garbage collectable"""
  426. assert dlg in self._dialogs
  427. self._dialogs.remove(dlg)
  428. qtrun = _QtRunner()
  429. def add(ui, *pats, **opts):
  430. """add files"""
  431. from tortoisehg.hgqt.quickop import run
  432. return qtrun(run, ui, *pats, **opts)
  433. def backout(ui, *pats, **opts):
  434. """backout tool"""
  435. from tortoisehg.hgqt.backout import run
  436. return qtrun(run, ui, *pats, **opts)
  437. def thgstatus(ui, *pats, **opts):
  438. """update TortoiseHg status cache"""
  439. from tortoisehg.util.thgstatus import run
  440. run(ui, *pats, **opts)
  441. def userconfig(ui, *pats, **opts):
  442. """user configuration editor"""
  443. from tortoisehg.hgqt.settings import run
  444. return qtrun(run, ui, *pats, **opts)
  445. def repoconfig(ui, *pats, **opts):
  446. """repository configuration editor"""
  447. from tortoisehg.hgqt.settings import run
  448. return qtrun(run, ui, *pats, **opts)
  449. def clone(ui, *pats, **opts):
  450. """clone tool"""
  451. from tortoisehg.hgqt.clone import run
  452. return qtrun(run, ui, *pats, **opts)
  453. def commit(ui, *pats, **opts):
  454. """commit tool"""
  455. from tortoisehg.hgqt.commit import run
  456. return qtrun(run, ui, *pats, **opts)
  457. def email(ui, *pats, **opts):
  458. """send changesets by email"""
  459. from tortoisehg.hgqt.hgemail import run
  460. return qtrun(run, ui, *pats, **opts)
  461. def resolve(ui, *pats, **opts):
  462. """resolve dialog"""
  463. from tortoisehg.hgqt.resolve import run
  464. return qtrun(run, ui, *pats, **opts)
  465. def postreview(ui, *pats, **opts):
  466. """post changesets to reviewboard"""
  467. from tortoisehg.hgqt.postreview import run
  468. return qtrun(run, ui, *pats, **opts)
  469. def merge(ui, *pats, **opts):
  470. """merge wizard"""
  471. from tortoisehg.hgqt.merge import run
  472. return qtrun(run, ui, *pats, **opts)
  473. def manifest(ui, *pats, **opts):
  474. """display the current or given revision of the project manifest"""
  475. from tortoisehg.hgqt.manifestdialog import run
  476. return qtrun(run, ui, *pats, **opts)
  477. def guess(ui, *pats, **opts):
  478. """guess previous renames or copies"""
  479. from tortoisehg.hgqt.guess import run
  480. return qtrun(run, ui, *pats, **opts)
  481. def status(ui, *pats, **opts):
  482. """browse working copy status"""
  483. from tortoisehg.hgqt.status import run
  484. return qtrun(run, ui, *pats, **opts)
  485. def shelve(ui, *pats, **opts):
  486. """Move changes between working directory and patches"""
  487. from tortoisehg.hgqt.shelve import run
  488. return qtrun(run, ui, *pats, **opts)
  489. def rejects(ui, *pats, **opts):
  490. """Manually resolve rejected patch chunks"""
  491. from tortoisehg.hgqt.rejects import run
  492. return qtrun(run, ui, *pats, **opts)
  493. def tag(ui, *pats, **opts):
  494. """tag tool"""
  495. from tortoisehg.hgqt.tag import run
  496. return qtrun(run, ui, *pats, **opts)
  497. def mq(ui, *pats, **opts):
  498. """Mercurial Queue tool"""
  499. from tortoisehg.hgqt.mq import run
  500. return qtrun(run, ui, *pats, **opts)
  501. def test(ui, *pats, **opts):
  502. """test arbitrary widgets"""
  503. from tortoisehg.hgqt.mq import run
  504. return qtrun(run, ui, *pats, **opts)
  505. def purge(ui, *pats, **opts):
  506. """purge unknown and/or ignore files from repository"""
  507. from tortoisehg.hgqt.purge import run
  508. return qtrun(run, ui, *pats, **opts)
  509. def qreorder(ui, *pats, **opts):
  510. """Reorder unapplied MQ patches"""
  511. from tortoisehg.hgqt.qreorder import run
  512. return qtrun(run, ui, *pats, **opts)
  513. def qqueue(ui, *pats, **opts):
  514. """manage multiple MQ patch queues"""
  515. from tortoisehg.hgqt.qqueue import run
  516. return qtrun(run, ui, *pats, **opts)
  517. def remove(ui, *pats, **opts):
  518. """remove selected files"""
  519. from tortoisehg.hgqt.quickop import run
  520. return qtrun(run, ui, *pats, **opts)
  521. def revert(ui, *pats, **opts):
  522. """revert selected files"""
  523. from tortoisehg.hgqt.quickop import run
  524. return qtrun(run, ui, *pats, **opts)
  525. def forget(ui, *pats, **opts):
  526. """forget selected files"""
  527. from tortoisehg.hgqt.quickop import run
  528. return qtrun(run, ui, *pats, **opts)
  529. def hgignore(ui, *pats, **opts):
  530. """ignore filter editor"""
  531. from tortoisehg.hgqt.hgignore import run
  532. return qtrun(run, ui, *pats, **opts)
  533. def serve(ui, *pats, **opts):
  534. """start stand-alone webserver"""
  535. from tortoisehg.hgqt.serve import run
  536. return qtrun(run, ui, *pats, **opts)
  537. def sync(ui, *pats, **opts):
  538. """Synchronize with other repositories"""
  539. from tortoisehg.hgqt.sync import run
  540. return qtrun(run, ui, *pats, **opts)
  541. def shellconfig(ui, *pats, **opts):
  542. """explorer extension configuration editor"""
  543. from tortoisehg.hgqt.shellconf import run
  544. return qtrun(run, ui, *pats, **opts)
  545. def update(ui, *pats, **opts):
  546. """update/checkout tool"""
  547. from tortoisehg.hgqt.update import run
  548. return qtrun(run, ui, *pats, **opts)
  549. def log(ui, *pats, **opts):
  550. """workbench application"""
  551. from tortoisehg.hgqt.workbench import run
  552. return qtrun(run, ui, *pats, **opts)
  553. def vdiff(ui, *pats, **opts):
  554. """launch configured visual diff tool"""
  555. from tortoisehg.hgqt.visdiff import run
  556. return qtrun(run, ui, *pats, **opts)
  557. def about(ui, *pats, **opts):
  558. """about dialog"""
  559. from tortoisehg.hgqt.about import run
  560. return qtrun(run, ui, *pats, **opts)
  561. def grep(ui, *pats, **opts):
  562. """grep/search dialog"""
  563. from tortoisehg.hgqt.grep import run
  564. return qtrun(run, ui, *pats, **opts)
  565. def archive(ui, *pats, **opts):
  566. """archive dialog"""
  567. from tortoisehg.hgqt.archive import run
  568. return qtrun(run, ui, *pats, **opts)
  569. def bisect(ui, *pats, **opts):
  570. """bisect dialog"""
  571. from tortoisehg.hgqt.bisect import run
  572. return qtrun(run, ui, *pats, **opts)
  573. def annotate(ui, *pats, **opts):
  574. """annotate dialog"""
  575. from tortoisehg.hgqt.annotate import run
  576. if len(pats) != 1:
  577. ui.warn(_('annotate requires a single filename\n'))
  578. return
  579. return qtrun(run, ui, *pats, **opts)
  580. def init(ui, *pats, **opts):
  581. """init dialog"""
  582. from tortoisehg.hgqt.hginit import run
  583. return qtrun(run, ui, *pats, **opts)
  584. def rename(ui, *pats, **opts):
  585. """rename dialog"""
  586. from tortoisehg.hgqt.rename import run
  587. return qtrun(run, ui, *pats, **opts)
  588. def strip(ui, *pats, **opts):
  589. """strip dialog"""
  590. from tortoisehg.hgqt.thgstrip import run
  591. return qtrun(run, ui, *pats, **opts)
  592. def rebase(ui, *pats, **opts):
  593. """rebase dialog"""
  594. from tortoisehg.hgqt.rebase import run
  595. return qtrun(run, ui, *pats, **opts)
  596. def thgimport(ui, *pats, **opts):
  597. """import an ordered set of patches"""
  598. from tortoisehg.hgqt.thgimport import run
  599. return qtrun(run, ui, *pats, **opts)
  600. ### help management, adapted from mercurial.commands.help_()
  601. def help_(ui, name=None, with_version=False, **opts):
  602. """show help for a command, extension, or list of commands
  603. With no arguments, print a list of commands and short help.
  604. Given a command name, print help for that command.
  605. Given an extension name, print help for that extension, and the
  606. commands it provides."""
  607. option_lists = []
  608. textwidth = ui.termwidth() - 2
  609. def addglobalopts(aliases):
  610. if ui.verbose:
  611. option_lists.append((_("global options:"), globalopts))
  612. if name == 'shortlist':
  613. option_lists.append((_('use "thg help" for the full list '
  614. 'of commands'), ()))
  615. else:
  616. if name == 'shortlist':
  617. msg = _('use "thg help" for the full list of commands '
  618. 'or "thg -v" for details')
  619. elif aliases:
  620. msg = _('use "thg -v help%s" to show aliases and '
  621. 'global options') % (name and " " + name or "")
  622. else:
  623. msg = _('use "thg -v help %s" to show global options') % name
  624. option_lists.append((msg, ()))
  625. def helpcmd(name):
  626. if with_version:
  627. version(ui)
  628. ui.write('\n')
  629. try:
  630. aliases, i = cmdutil.findcmd(name, table, False)
  631. except error.AmbiguousCommand, inst:
  632. select = lambda c: c.lstrip('^').startswith(inst.args[0])
  633. helplist(_('list of commands:\n\n'), select)
  634. return
  635. # synopsis
  636. ui.write("%s\n" % i[2])
  637. # aliases
  638. if not ui.quiet and len(aliases) > 1:
  639. ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
  640. # description
  641. doc = i[0].__doc__
  642. if not doc:
  643. doc = _("(No help text available)")
  644. if ui.quiet:
  645. doc = doc.splitlines(0)[0]
  646. ui.write("\n%s\n" % doc.rstrip())
  647. if not ui.quiet:
  648. # options
  649. if i[1]:
  650. option_lists.append((_("options:\n"), i[1]))
  651. addglobalopts(False)
  652. def helplist(header, select=None):
  653. h = {}
  654. cmds = {}
  655. for c, e in table.iteritems():
  656. f = c.split("|", 1)[0]
  657. if select and not select(f):
  658. continue
  659. if (not select and name != 'shortlist' and
  660. e[0].__module__ != __name__):
  661. continue
  662. if name == "shortlist" and not f.startswith("^"):
  663. continue
  664. f = f.lstrip("^")
  665. if not ui.debugflag and f.startswith("debug"):
  666. continue
  667. doc = e[0].__doc__
  668. if doc and 'DEPRECATED' in doc and not ui.verbose:
  669. continue
  670. #doc = gettext(doc)
  671. if not doc:
  672. doc = _("(no help text available)")
  673. h[f] = doc.splitlines()[0].rstrip()
  674. cmds[f] = c.lstrip("^")
  675. if not h:
  676. ui.status(_('no commands defined\n'))
  677. return
  678. ui.status(header)
  679. fns = sorted(h)
  680. m = max(map(len, fns))
  681. for f in fns:
  682. if ui.verbose:
  683. commands = cmds[f].replace("|",", ")
  684. ui.write(" %s:\n %s\n"%(commands, h[f]))
  685. else:
  686. ui.write('%s\n' % (util.wrap(h[f], textwidth,
  687. initindent=' %-*s ' % (m, f),
  688. hangindent=' ' * (m + 4))))
  689. if not ui.quiet:
  690. addglobalopts(True)
  691. def helptopic(name):
  692. from mercurial import help
  693. for names, header, doc in help.helptable:
  694. if name in names:
  695. break
  696. else:
  697. raise error.UnknownCommand(name)
  698. # description
  699. if not doc:
  700. doc = _("(No help text available)")
  701. if hasattr(doc, '__call__'):
  702. doc = doc()
  703. ui.write("%s\n" % header)
  704. ui.write("%s\n" % doc.rstrip())
  705. if name and name != 'shortlist':
  706. i = None
  707. for f in (helpcmd, helptopic):
  708. try:
  709. f(name)
  710. i = None
  711. break
  712. except error.UnknownCommand, inst:
  713. i = inst
  714. if i:
  715. raise i
  716. else:
  717. # program name
  718. if ui.verbose or with_version:
  719. version(ui)
  720. else:
  721. ui.status(_("Thg - TortoiseHg's GUI tools for Mercurial SCM (Hg)\n"))
  722. ui.status('\n')
  723. # list of commands
  724. if name == "shortlist":
  725. header = _('basic commands:\n\n')
  726. else:
  727. header = _('list of commands:\n\n')
  728. helplist(header)
  729. # list all option lists
  730. opt_output = []
  731. for title, options in option_lists:
  732. opt_output.append(("\n%s" % title, None))
  733. for shortopt, longopt, default, desc in options:
  734. if "DEPRECATED" in desc and not ui.verbose: continue
  735. opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
  736. longopt and " --%s" % longopt),
  737. "%s%s" % (desc,
  738. default
  739. and _(" (default: %s)") % default
  740. or "")))
  741. if opt_output:
  742. opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
  743. for first, second in opt_output:
  744. if second:
  745. initindent = ' %-*s ' % (opts_len, first)
  746. hangindent = ' ' * (opts_len + 3)
  747. ui.write('%s\n' % (util.wrap(second, textwidth,
  748. initindent=initindent,
  749. hangindent=hangindent)))
  750. else:
  751. ui.write("%s\n" % first)
  752. def version(ui, **opts):
  753. """output version and copyright information"""
  754. ui.write(_('TortoiseHg Dialogs (version %s), '
  755. 'Mercurial (version %s)\n') %
  756. (thgversion.version(), hglib.hgversion))
  757. if not ui.quiet:
  758. ui.write(shortlicense)
  759. def debugcomplete(ui, cmd='', **opts):
  760. """output list of possible commands"""
  761. if opts.get('options'):
  762. options = []
  763. otables = [globalopts]
  764. if cmd:
  765. aliases, entry = cmdutil.findcmd(cmd, table, False)
  766. otables.append(entry[1])
  767. for t in otables:
  768. for o in t:
  769. if o[0]:
  770. options.append('-%s' % o[0])
  771. options.append('--%s' % o[1])
  772. ui.write("%s\n" % "\n".join(options))
  773. return
  774. cmdlist = cmdutil.findpossible(cmd, table)
  775. if ui.verbose:
  776. cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
  777. ui.write("%s\n" % "\n".join(sorted(cmdlist)))
  778. globalopts = [
  779. ('R', 'repository', '',
  780. _('repository root directory or symbolic path name')),
  781. ('v', 'verbose', None, _('enable additional output')),
  782. ('q', 'quiet', None, _('suppress output')),
  783. ('h', 'help', None, _('display help and exit')),
  784. ('', 'debugger', None, _('start debugger')),
  785. ('', 'profile', None, _('print command execution profile')),
  786. ('', 'nofork', None, _('do not fork GUI process')),
  787. ('', 'fork', None, _('always fork GUI process')),
  788. ('', 'listfile', '', _('read file list from file')),
  789. ('', 'listfileutf8', '', _('read file list from file encoding utf-8')),
  790. ]
  791. table = {
  792. "about": (about, [], _('thg about')),
  793. "add": (add, [], _('thg add [FILE]...')),
  794. "^annotate|blame": (annotate,
  795. [('r', 'rev', '', _('revision to annotate')),
  796. ('n', 'line', '', _('open to line')),
  797. ('p', 'pattern', '', _('initial search pattern'))],
  798. _('thg annotate')),
  799. "archive": (archive,
  800. [('r', 'rev', '', _('revision to archive'))],
  801. _('thg archive')),
  802. "^backout": (backout,
  803. [('', 'merge', None,
  804. _('merge with old dirstate parent after backout')),
  805. ('', 'parent', '', _('parent to choose when backing out merge')),
  806. ('r', 'rev', '', _('revision to backout'))],
  807. _('thg backout [OPTION]... [[-r] REV]')),
  808. "^bisect": (bisect, [], _('thg bisect')),
  809. "^clone":
  810. (clone,
  811. [('U', 'noupdate', None,
  812. _('the clone will include an empty working copy '
  813. '(only a repository)')),
  814. ('u', 'updaterev', '',
  815. _('revision, tag or branch to check out')),
  816. ('r', 'rev', [], _('include the specified changeset')),
  817. ('b', 'branch', [],
  818. _('clone only the specified branch')),
  819. ('', 'pull', None, _('use pull protocol to copy metadata')),
  820. ('', 'uncompressed', None,
  821. _('use uncompressed transfer (fast over LAN)')),],
  822. _('thg clone [OPTION]... SOURCE [DEST]')),
  823. "^commit|ci": (commit,
  824. [('u', 'user', '', _('record user as committer')),
  825. ('d', 'date', '', _('record datecode as commit date'))],
  826. _('thg commit [OPTIONS] [FILE]...')),
  827. "^grep|search": (grep,
  828. [('i', 'ignorecase', False, _('ignore case during search')),],
  829. _('thg grep')),
  830. "^guess": (guess, [], _('thg guess')),
  831. "^hgignore|ignore|filter": (hgignore, [], _('thg hgignore [FILE]')),
  832. "import": (thgimport,
  833. [('', 'repo', False, _('import to the repository')),
  834. ('', 'mq', False, _('import to the patch queue (MQ)'))],
  835. _('thg import [OPTION] [SOURCE]...')),
  836. "^init": (init, [], _('thg init [DEST]')),
  837. "^email":
  838. (email,
  839. [('r', 'rev', [], _('a revision to send')),],
  840. _('thg email [REVS]')),
  841. "^log|history|explorer|workbench":
  842. (log,
  843. [('l', 'limit', '', _('limit number of changes displayed'))],
  844. _('thg log [OPTIONS] [FILE]')),
  845. "manifest":
  846. (manifest,
  847. [('r', 'rev', '', _('revision to display')),
  848. ('n', 'line', '', _('open to line')),
  849. ('p', 'pattern', '', _('initial search pattern'))],
  850. _('thg manifest [-r REV] [FILE]')),
  851. "^merge":
  852. (merge,
  853. [('r', 'rev', '', _('revision to merge'))],
  854. _('thg merge [[-r] REV]')),
  855. "remove|rm": (remove, [], _('thg remove [FILE]...')),
  856. "mq": (mq, [], _('thg mq')),
  857. "resolve": (resolve, [], _('thg resolve')),
  858. "revert": (revert, [], _('thg revert [FILE]...')),
  859. "forget": (forget, [], _('thg forget [FILE]...')),
  860. "rename|mv|copy": (rename, [], _('thg rename SOURCE [DEST]...')),
  861. "^serve":
  862. (serve,
  863. [('', 'web-conf', '',
  864. _('name of the hgweb config file (serve more than one repository)')),
  865. ('', 'webdir-conf', '',
  866. _('name of the hgweb config file (DEPRECATED)'))],
  867. _('thg serve [--web-conf FILE]')),
  868. "^sync|synchronize": (sync, [], _('thg sync')),
  869. "^status": (status,
  870. [('c', 'clean', False, _('show files without changes')),
  871. ('i', 'ignored', False, _('show ignored files'))],
  872. _('thg status [OPTIONS] [FILE]')),
  873. "^strip": (strip,
  874. [('f', 'force', None, _('discard uncommitted changes (no backup)')),
  875. ('n', 'nobackup', None, _('do not back up stripped revisions')),
  876. ('r', 'rev', '', _('revision to strip')),],
  877. _('thg strip [-f] [-n] [[-r] REV]')),
  878. "^rebase": (rebase,
  879. [('', 'keep', False, _('keep original changesets')),
  880. ('', 'detach', False, _('force detaching of source from its original '
  881. 'branch')),
  882. ('s', 'source', '',
  883. _('rebase from the specified changeset')),
  884. ('d', 'dest', '',
  885. _('rebase onto the specified changeset'))],
  886. _('thg rebase -s REV -d REV [--keep] [--detach]')),
  887. "^tag":
  888. (tag,
  889. [('f', 'force', None, _('replace existing tag')),
  890. ('l', 'local', None, _('make the tag local')),
  891. ('r', 'rev', '', _('revision to tag')),
  892. ('', 'remove', None, _('remove a tag')),
  893. ('m', 'message', '', _('use <text> as commit message')),],
  894. _('thg tag [-f] [-l] [-m TEXT] [-r REV] [NAME]')),
  895. "shelve|unshelve": (shelve, [], _('thg shelve')),
  896. "rejects": (rejects, [], _('thg rejects [FILE]')),
  897. "test": (test, [], _('thg test')),
  898. "help": (help_, [], _('thg help [COMMAND]')),
  899. "^purge": (purge, [], _('thg purge')),
  900. "^qreorder": (qreorder, [], _('thg qreorder')),
  901. "^qqueue": (qqueue, [], _('thg qqueue')),
  902. "^update|checkout|co":
  903. (update,
  904. [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
  905. ('r', 'rev', '', _('revision to update')),],
  906. _('thg update [-C] [[-r] REV]')),
  907. "^userconfig": (userconfig,
  908. [('', 'focus', '', _('field to give initial focus'))],
  909. _('thg userconfig')),
  910. "^repoconfig": (repoconfig,
  911. [('', 'focus', '', _('field to give initial focus'))],
  912. _('thg repoconfig')),
  913. "^vdiff": (vdiff,
  914. [('c', 'change', '', _('changeset to view in diff tool')),
  915. ('r', 'rev', [], _('revisions to view in diff tool')),
  916. ('b', 'bundle', '', _('bundle file to preview'))],
  917. _('launch visual diff tool')),
  918. "^version": (version,
  919. [('v', 'verbose', None, _('print license'))],
  920. _('thg version [OPTION]')),
  921. }
  922. if os.name == 'nt':
  923. # TODO: extra detection to determine if shell extension is installed
  924. table['shellconfig'] = (shellconfig, [], _('thg shellconfig'))