PageRenderTime 139ms CodeModel.GetById 64ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgqt/run.py

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