# run.py - front-end script for TortoiseHg dialogs # # Copyright 2008 Steve Borho # Copyright 2008 TK Soh # # This software may be used and distributed according to the terms of the # GNU General Public License version 2, incorporated herein by reference. from __future__ import absolute_import import getopt import os import pdb import sys import subprocess from mercurial import ( cmdutil, error, extensions, fancyopts, pathutil, registrar, scmutil, util, ) from mercurial.utils import ( stringutil, ) from ..util import ( hglib, i18n, paths, version as thgversion, ) from ..util.i18n import agettext as _ from . import ( cmdui, qtapp, qtlib, quickop, thgrepo, ) try: from tortoisehg.util.config import nofork as config_nofork except ImportError: config_nofork = None shortlicense = ''' Copyright (C) 2008-2018 Steve Borho and others. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ''' console_commands = 'help thgstatus version' nonrepo_commands = '''userconfig shellconfig clone init debugblockmatcher debugbugreport about help version thgstatus serve rejects log''' def dispatch(args, u=None): """run the command specified in args""" try: if u is None: u = hglib.loadui() if '--traceback' in args: u.setconfig('ui', 'traceback', 'on') if '--debugger' in args: pdb.set_trace() return _runcatch(u, args) except error.ParseError, e: qtapp.earlyExceptionMsgBox(hglib.tounicode(str(e))) except SystemExit, e: return e.code except Exception, e: if '--debugger' in args: pdb.post_mortem(sys.exc_info()[2]) qtapp.earlyBugReport(e) return -1 except KeyboardInterrupt: print _('\nCaught keyboard interrupt, aborting.\n') return -1 def portable_fork(ui, opts): if 'THG_GUI_SPAWN' in os.environ or ( not opts.get('fork') and opts.get('nofork')): os.environ['THG_GUI_SPAWN'] = '1' return elif 'THG_OSX_APP' in os.environ: # guifork seems to break Mac app bundles return elif ui.configbool('tortoisehg', 'guifork', None) is not None: if not ui.configbool('tortoisehg', 'guifork'): return elif config_nofork: return os.environ['THG_GUI_SPAWN'] = '1' try: _forkbg(ui) except OSError, inst: ui.warn(_('failed to fork GUI process: %s\n') % inst.strerror) # native window API can't be used after fork() on Mac OS X if os.name == 'posix' and sys.platform != 'darwin': def _forkbg(ui): pid = os.fork() if pid > 0: sys.exit(0) # disables interaction with tty, keeping logs sent to stdout/stderr nullfd = os.open(os.devnull, os.O_RDONLY) os.dup2(nullfd, ui.fin.fileno()) os.close(nullfd) else: _origwdir = os.getcwd() def _forkbg(ui): # Spawn background process and exit cmdline = list(paths.get_thg_command()) cmdline.extend(sys.argv[1:]) os.chdir(_origwdir) # redirect stdout/stderr to "nul" device; otherwise EBADF could occur # on cmd.exe (#4837). for stdin, create a pipe since "nul" is a tty # on Windows! (#4469) nullfd = os.open(os.devnull, os.O_RDWR) p = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=nullfd, stderr=nullfd, creationflags=qtlib.openflags) p.stdin.close() os.close(nullfd) sys.exit(0) # Windows and Nautilus shellext execute # "thg subcmd --listfile TMPFILE" or "thg subcmd --listfileutf8 TMPFILE"(planning) . # Extensions written in .hg/hgrc is enabled after calling # extensions.loadall(lui) # # 1. win32mbcs extension # Japanese shift_jis and Chinese big5 include '0x5c'(backslash) in filename. # Mercurial resolves this problem with win32mbcs extension. # So, thg must parse path after loading win32mbcs extension. # # 2. fixutf8 extension # fixutf8 extension requires paths encoding utf-8. # So, thg need to convert to utf-8. # _lines = [] _linesutf8 = [] def get_lines_from_listfile(filename, isutf8): global _lines global _linesutf8 try: if filename == '-': lines = [ x.replace("\n", "") for x in sys.stdin.readlines() ] else: fd = open(filename, "r") lines = [ x.replace("\n", "") for x in fd.readlines() ] fd.close() os.unlink(filename) if isutf8: _linesutf8 = lines else: _lines = lines except IOError: sys.stderr.write(_('can not read file "%s". Ignored.\n') % filename) def get_files_from_listfile(): global _lines global _linesutf8 lines = [] need_to_utf8 = False if os.name == 'nt': try: fixutf8 = extensions.find("fixutf8") if fixutf8: need_to_utf8 = True except KeyError: pass if need_to_utf8: lines += _linesutf8 for l in _lines: lines.append(hglib.toutf(l)) else: lines += _lines for l in _linesutf8: lines.append(hglib.fromutf(l)) # Convert absolute file paths to repo/cwd canonical cwd = os.getcwd() root = paths.find_root(cwd) if not root: return lines if cwd == root: cwd_rel = '' else: cwd_rel = cwd[len(root+os.sep):] + os.sep files = [] for f in lines: try: cpath = pathutil.canonpath(root, cwd, f) # canonpath will abort on .hg/ paths except error.Abort: continue if cpath.startswith(cwd_rel): cpath = cpath[len(cwd_rel):] files.append(cpath) else: files.append(f) return files def _parse(ui, args): options = {} cmdoptions = {} try: args = fancyopts.fancyopts(args, globalopts, options) except getopt.GetoptError, inst: raise error.CommandError(None, inst) if args: alias, args = args[0], args[1:] elif options['help']: help_(ui, None) sys.exit() else: alias, args = 'workbench', [] aliases, i = cmdutil.findcmd(alias, table, ui.config("ui", "strict")) for a in aliases: if a.startswith(alias): alias = a break cmd = aliases[0] c = list(i[1]) # combine global options into local for o in globalopts: c.append((o[0], o[1], options[o[1]], o[3])) try: args = fancyopts.fancyopts(args, c, cmdoptions, True) except getopt.GetoptError, inst: raise error.CommandError(cmd, inst) # separate global options back out for o in globalopts: n = o[1] options[n] = cmdoptions[n] del cmdoptions[n] listfile = options.get('listfile') if listfile: del options['listfile'] get_lines_from_listfile(listfile, False) listfileutf8 = options.get('listfileutf8') if listfileutf8: del options['listfileutf8'] get_lines_from_listfile(listfileutf8, True) return (cmd, cmd and i[0] or None, args, options, cmdoptions, alias) def _runcatch(ui, args): try: # read --config before doing anything else like Mercurial hglib.parseconfigopts(ui, args) # register config items specific to TortoiseHg GUI ui.setconfig('extensions', 'tortoisehg.util.configitems', '', 'run') try: return runcommand(ui, args) finally: ui.flush() except error.AmbiguousCommand, inst: ui.warn(_("thg: command '%s' is ambiguous:\n %s\n") % (inst.args[0], " ".join(inst.args[1]))) except error.UnknownCommand, inst: ui.warn(_("thg: unknown command '%s'\n") % inst.args[0]) help_(ui, 'shortlist') except error.CommandError, inst: if inst.args[0]: ui.warn(_("thg %s: %s\n") % (inst.args[0], inst.args[1])) help_(ui, inst.args[0]) else: ui.warn(_("thg: %s\n") % inst.args[1]) help_(ui, 'shortlist') except error.RepoError, inst: ui.warn(_("abort: %s!\n") % inst) except error.Abort, inst: ui.warn(_("abort: %s\n") % inst) if inst.hint: ui.warn(_("(%s)\n") % inst.hint) return -1 def runcommand(ui, args): cmd, func, args, options, cmdoptions, alias = _parse(ui, args) if options['config']: raise error.Abort(_('option --config may not be abbreviated!')) cmdoptions['alias'] = alias ui.setconfig("ui", "verbose", str(bool(options["verbose"]))) ui.setconfig('ui', 'debug', str(bool(options['debug'] or 'THGDEBUG' in os.environ))) i18n.setlanguage(ui.config('tortoisehg', 'ui.language')) if options['help']: return help_(ui, cmd) path = options['repository'] if path: if path.startswith('bundle:'): s = path[7:].split('+', 1) if len(s) == 1: path, bundle = os.getcwd(), s[0] else: path, bundle = s cmdoptions['bundle'] = os.path.abspath(bundle) path = ui.expandpath(path) # TODO: replace by abspath() if chdir() isn't necessary try: os.chdir(path) path = os.getcwd() except OSError: pass if options['profile']: options['nofork'] = True path = paths.find_root(path) if path: try: lui = ui.copy() lui.readconfig(os.path.join(path, ".hg", "hgrc")) except IOError: pass else: lui = ui hglib.loadextensions(lui) args += get_files_from_listfile() if options['quiet']: ui.quiet = True # repository existence will be tested in qtrun() if cmd not in nonrepo_commands.split(): cmdoptions['repository'] = path or options['repository'] or '.' cmdoptions['mainapp'] = True checkedfunc = util.checksignature(func) if cmd in console_commands.split(): d = lambda: checkedfunc(ui, *args, **cmdoptions) else: # disables interaction with tty as it would block GUI events ui.setconfig('ui', 'interactive', 'off', 'qtrun') ui.setconfig('ui', 'paginate', 'off', 'qtrun') # ANSI color output is useless in GUI ui.setconfig('ui', 'color', 'off', 'qtrun') portable_fork(ui, options) d = lambda: qtrun(checkedfunc, ui, *args, **cmdoptions) return _runcommand(lui, options, cmd, d) def _runcommand(ui, options, cmd, cmdfunc): def checkargs(): try: return cmdfunc() except error.SignatureError: raise error.CommandError(cmd, _("invalid arguments")) if options['profile']: format = ui.config('profiling', 'format', default='text') if not format in ['text', 'kcachegrind']: ui.warn(_("unrecognized profiling format '%s'" " - Ignored\n") % format) format = 'text' output = ui.config('profiling', 'output') if output: path = ui.expandpath(output) ostream = open(path, 'wb') else: ostream = sys.stderr try: from mercurial import lsprof except ImportError: raise error.Abort(_( 'lsprof not available - install from ' 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/')) p = lsprof.Profiler() p.enable(subcalls=True) try: return checkargs() finally: p.disable() if format == 'kcachegrind': import lsprofcalltree calltree = lsprofcalltree.KCacheGrind(p) calltree.output(ostream) else: # format == 'text' stats = lsprof.Stats(p.getstats()) stats.sort() stats.pprint(top=10, file=ostream, climit=5) if output: ostream.close() else: return checkargs() qtrun = qtapp.QtRunner() table = {} command = registrar.command(table) # common command options globalopts = [ ('R', 'repository', '', _('repository root directory or symbolic path name')), ('v', 'verbose', None, _('enable additional output')), ('q', 'quiet', None, _('suppress output')), ('h', 'help', None, _('display help and exit')), ('', 'config', [], _("set/override config option (use 'section.name=value')")), ('', 'debug', None, _('enable debugging output')), ('', 'debugger', None, _('start debugger')), ('', 'profile', None, _('print command execution profile')), ('', 'nofork', None, _('do not fork GUI process')), ('', 'fork', None, _('always fork GUI process')), ('', 'listfile', '', _('read file list from file')), ('', 'listfileutf8', '', _('read file list from file encoding utf-8')), ] # common command functions def _formatfilerevset(pats): q = ["file('path:%s')" % f for f in hglib.canonpaths(pats)] return ' or '.join(q) def _workbench(ui, *pats, **opts): root = opts.get('root') or paths.find_root() # TODO: unclear that _workbench() is called inside qtrun(). maybe this # function should receive factory object instead of using global qtrun. w = qtrun.createWorkbench() if root: root = hglib.tounicode(root) bundle = opts.get('bundle') if bundle: w.openRepo(root, False, bundle=hglib.tounicode(bundle)) else: w.showRepo(root) rev = opts.get('rev') if rev: w.goto(hglib.fromunicode(root), rev) q = opts.get('query') or _formatfilerevset(pats) if q: w.setRevsetFilter(root, hglib.tounicode(q)) if not w.currentRepoRootPath(): w.reporegistry.setVisible(True) return w # commands start here, listed alphabetically @command('about', [], _('thg about')) def about(ui, *pats, **opts): """about dialog""" from tortoisehg.hgqt import about as aboutmod return aboutmod.AboutDialog() @command('add', [], _('thg add [FILE]...')) def add(ui, repoagent, *pats, **opts): """add files""" return quickop.run(ui, repoagent, *pats, **opts) @command('^annotate|blame', [('r', 'rev', '', _('revision to annotate')), ('n', 'line', '', _('open to line')), ('p', 'pattern', '', _('initial search pattern'))], _('thg annotate')) def annotate(ui, repoagent, *pats, **opts): """annotate dialog""" from tortoisehg.hgqt import fileview dlg = filelog(ui, repoagent, *pats, **opts) dlg.setFileViewMode(fileview.AnnMode) if opts.get('line'): try: lineno = int(opts['line']) except ValueError: raise error.Abort(_('invalid line number: %s') % opts['line']) dlg.showLine(lineno) if opts.get('pattern'): dlg.setSearchPattern(hglib.tounicode(opts['pattern'])) return dlg @command('archive', [('r', 'rev', '', _('revision to archive'))], _('thg archive')) def archive(ui, repoagent, *pats, **opts): """archive dialog""" from tortoisehg.hgqt import archive as archivemod rev = opts.get('rev') return archivemod.createArchiveDialog(repoagent, rev) @command('^backout', [('', 'merge', None, _('merge with old dirstate parent after backout')), ('', 'parent', '', _('parent to choose when backing out merge')), ('r', 'rev', '', _('revision to backout'))], _('thg backout [OPTION]... [[-r] REV]')) def backout(ui, repoagent, *pats, **opts): """backout tool""" from tortoisehg.hgqt import backout as backoutmod if opts.get('rev'): rev = opts.get('rev') elif len(pats) == 1: rev = pats[0] else: rev = 'tip' repo = repoagent.rawRepo() rev = scmutil.revsingle(repo, rev).rev() msg = backoutmod.checkrev(repo, rev) if msg: raise error.Abort(hglib.fromunicode(msg)) return backoutmod.BackoutDialog(repoagent, rev) @command('^bisect', [], _('thg bisect')) def bisect(ui, repoagent, *pats, **opts): """bisect dialog""" from tortoisehg.hgqt import bisect as bisectmod return bisectmod.BisectDialog(repoagent) @command('bookmarks|bookmark', [('r', 'rev', '', _('revision'))], _('thg bookmarks [-r REV] [NAME]')) def bookmark(ui, repoagent, *names, **opts): """add or remove a movable marker""" from tortoisehg.hgqt import bookmark as bookmarkmod repo = repoagent.rawRepo() rev = scmutil.revsingle(repo, opts.get('rev')).rev() if len(names) > 1: raise error.Abort(_('only one new bookmark name allowed')) dlg = bookmarkmod.BookmarkDialog(repoagent, rev) if names: dlg.setBookmarkName(hglib.tounicode(names[0])) return dlg @command('^clone', [('U', 'noupdate', None, _('the clone will include an empty working copy ' '(only a repository)')), ('u', 'updaterev', '', _('revision, tag or branch to check out')), ('r', 'rev', '', _('include the specified changeset')), ('b', 'branch', [], _('clone only the specified branch')), ('', 'pull', None, _('use pull protocol to copy metadata')), ('', 'uncompressed', None, _('use uncompressed transfer ' '(fast over LAN)'))], _('thg clone [OPTION]... [SOURCE] [DEST]')) def clone(ui, *pats, **opts): """clone tool""" from tortoisehg.hgqt import clone as clonemod dlg = clonemod.CloneDialog(ui, pats, opts) dlg.clonedRepository.connect(qtrun.openRepoInWorkbench) return dlg @command('^commit|ci', [('u', 'user', '', _('record user as committer')), ('d', 'date', '', _('record datecode as commit date'))], _('thg commit [OPTIONS] [FILE]...')) def commit(ui, repoagent, *pats, **opts): """commit tool""" from tortoisehg.hgqt import commit as commitmod repo = repoagent.rawRepo() pats = hglib.canonpaths(pats) os.chdir(repo.root) return commitmod.CommitDialog(repoagent, pats, opts) @command('debugblockmatcher', [], _('thg debugblockmatcher')) def debugblockmatcher(ui, *pats, **opts): """show blockmatcher widget""" from tortoisehg.hgqt import blockmatcher return blockmatcher.createTestWidget(ui) @command('debugbugreport', [], _('thg debugbugreport [TEXT]')) def debugbugreport(ui, *pats, **opts): """open bugreport dialog by exception""" raise Exception(' '.join(pats)) @command('debugconsole', [], _('thg debugconsole')) def debugconsole(ui, repoagent, *pats, **opts): """open console window""" from tortoisehg.hgqt import docklog dlg = docklog.ConsoleWidget(repoagent) dlg.closeRequested.connect(dlg.close) dlg.resize(700, 400) return dlg @command('debuglighthg', [], _('thg debuglighthg')) def debuglighthg(ui, repoagent, *pats, **opts): from tortoisehg.hgqt import repowidget return repowidget.LightRepoWindow(repoagent) @command('debugruncommand', [], _('thg debugruncommand -- COMMAND [ARGUMENT]...')) def debugruncommand(ui, repoagent, *cmdline, **opts): """run hg command in dialog""" if not cmdline: raise error.Abort(_('no command specified')) dlg = cmdui.CmdSessionDialog() dlg.setLogVisible(ui.verbose) sess = repoagent.runCommand(map(hglib.tounicode, cmdline), dlg) dlg.setSession(sess) return dlg @command('drag_copy', [], _('thg drag_copy SOURCE... DEST')) def drag_copy(ui, repoagent, *pats, **opts): """copy the selected files to the desired directory""" opts.update(alias='copy', headless=True) return quickop.run(ui, repoagent, *pats, **opts) @command('drag_move', [], _('thg drag_move SOURCE... DEST')) def drag_move(ui, repoagent, *pats, **opts): """move the selected files to the desired directory""" opts.update(alias='move', headless=True) return quickop.run(ui, repoagent, *pats, **opts) @command('^email', [('r', 'rev', [], _('a revision to send'))], _('thg email [REVS]')) def email(ui, repoagent, *revs, **opts): """send changesets by email""" from tortoisehg.hgqt import hgemail # TODO: same options as patchbomb if opts.get('rev'): if revs: raise error.Abort(_('use only one form to specify the revision')) revs = opts.get('rev') repo = repoagent.rawRepo() revs = scmutil.revrange(repo, revs) return hgemail.EmailDialog(repoagent, revs) @command('^filelog', [('r', 'rev', '', _('select the specified revision')), ('', 'compare', False, _('side-by-side comparison of revisions'))], _('thg filelog [OPTION]... FILE')) def filelog(ui, repoagent, *pats, **opts): """show history of the specified file""" from tortoisehg.hgqt import filedialogs if len(pats) != 1: raise error.Abort(_('requires a single filename')) repo = repoagent.rawRepo() rev = scmutil.revsingle(repo, opts.get('rev')).rev() filename = hglib.canonpaths(pats)[0] if opts.get('compare'): dlg = filedialogs.FileDiffDialog(repoagent, filename) else: dlg = filedialogs.FileLogDialog(repoagent, filename) dlg.goto(rev) return dlg @command('forget', [], _('thg forget [FILE]...')) def forget(ui, repoagent, *pats, **opts): """forget selected files""" return quickop.run(ui, repoagent, *pats, **opts) @command('graft', [('r', 'rev', [], _('revisions to graft'))], _('thg graft [-r] REV...')) def graft(ui, repoagent, *revs, **opts): """graft dialog""" from tortoisehg.hgqt import graft as graftmod repo = repoagent.rawRepo() revs = list(revs) revs.extend(opts['rev']) if not os.path.exists(repo.vfs.join('graftstate')) and not revs: raise error.Abort(_('You must provide revisions to graft')) return graftmod.GraftDialog(repoagent, None, source=revs) @command('^grep|search', [('i', 'ignorecase', False, _('ignore case during search'))], _('thg grep')) def grep(ui, repoagent, *pats, **opts): """grep/search dialog""" from tortoisehg.hgqt import grep as grepmod upats = [hglib.tounicode(p) for p in pats] return grepmod.SearchDialog(repoagent, upats, **opts) @command('^guess', [], _('thg guess')) def guess(ui, repoagent, *pats, **opts): """guess previous renames or copies""" from tortoisehg.hgqt import guess as guessmod return guessmod.DetectRenameDialog(repoagent, None, *pats) ### help management, adapted from mercurial.commands.help_() @command('help', [], _('thg help [COMMAND]')) def help_(ui, name=None, with_version=False, **opts): """show help for a command, extension, or list of commands With no arguments, print a list of commands and short help. Given a command name, print help for that command. Given an extension name, print help for that extension, and the commands it provides.""" option_lists = [] textwidth = ui.termwidth() - 2 def addglobalopts(aliases): if ui.verbose: option_lists.append((_("global options:"), globalopts)) if name == 'shortlist': option_lists.append((_('use "thg help" for the full list ' 'of commands'), ())) else: if name == 'shortlist': msg = _('use "thg help" for the full list of commands ' 'or "thg -v" for details') elif aliases: msg = _('use "thg -v help%s" to show aliases and ' 'global options') % (name and " " + name or "") else: msg = _('use "thg -v help %s" to show global options') % name option_lists.append((msg, ())) def helpcmd(name): if with_version: version(ui) ui.write('\n') try: aliases, i = cmdutil.findcmd(name, table, False) except error.AmbiguousCommand, inst: select = lambda c: c.lstrip('^').startswith(inst.args[0]) helplist(_('list of commands:\n\n'), select) return # synopsis ui.write("%s\n" % i[2]) # aliases if not ui.quiet and len(aliases) > 1: ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:])) # description doc = i[0].__doc__ if not doc: doc = _("(no help text available)") if ui.quiet: doc = doc.splitlines(0)[0] ui.write("\n%s\n" % doc.rstrip()) if not ui.quiet: # options if i[1]: option_lists.append((_("options:\n"), i[1])) addglobalopts(False) def helplist(header, select=None): h = {} cmds = {} for c, e in table.iteritems(): f = c.split("|", 1)[0] if select and not select(f): continue if (not select and name != 'shortlist' and e[0].__module__ != __name__): continue if name == "shortlist" and not f.startswith("^"): continue f = f.lstrip("^") if not ui.debugflag and f.startswith("debug"): continue doc = e[0].__doc__ if doc and 'DEPRECATED' in doc and not ui.verbose: continue #doc = gettext(doc) if not doc: doc = _("(no help text available)") h[f] = doc.splitlines()[0].rstrip() cmds[f] = c.lstrip("^") if not h: ui.status(_('no commands defined\n')) return ui.status(header) fns = sorted(h) m = max(map(len, fns)) for f in fns: if ui.verbose: commands = cmds[f].replace("|",", ") ui.write(" %s:\n %s\n"%(commands, h[f])) else: ui.write('%s\n' % (stringutil.wrap(h[f], textwidth, initindent=' %-*s ' % (m, f), hangindent=' ' * (m + 4)))) if not ui.quiet: addglobalopts(True) def helptopic(name): from mercurial import help for names, header, doc in help.helptable: if name in names: break else: raise error.UnknownCommand(name) # description if not doc: doc = _("(no help text available)") if hasattr(doc, '__call__'): doc = doc() ui.write("%s\n" % header) ui.write("%s\n" % doc.rstrip()) if name and name != 'shortlist': i = None for f in (helpcmd, helptopic): try: f(name) i = None break except error.UnknownCommand, inst: i = inst if i: raise i else: # program name if ui.verbose or with_version: version(ui) else: ui.status(_("Thg - TortoiseHg's GUI tools for Mercurial SCM (Hg)\n")) ui.status('\n') # list of commands if name == "shortlist": header = _('basic commands:\n\n') else: header = _('list of commands:\n\n') helplist(header) # list all option lists opt_output = [] for title, options in option_lists: opt_output.append(("\n%s" % title, None)) for shortopt, longopt, default, desc in options: if "DEPRECATED" in desc and not ui.verbose: continue opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt, longopt and " --%s" % longopt), "%s%s" % (desc, default and _(" (default: %s)") % default or ""))) if opt_output: opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0]) for first, second in opt_output: if second: initindent = ' %-*s ' % (opts_len, first) hangindent = ' ' * (opts_len + 3) ui.write('%s\n' % (stringutil.wrap(second, textwidth, initindent=initindent, hangindent=hangindent))) else: ui.write("%s\n" % first) @command('^hgignore|ignore|filter', [], _('thg hgignore [FILE]')) def hgignore(ui, repoagent, *pats, **opts): """ignore filter editor""" from tortoisehg.hgqt import hgignore as hgignoremod if pats and pats[0].endswith('.hgignore'): pats = [] return hgignoremod.HgignoreDialog(repoagent, None, *pats) @command('import', [('', 'mq', False, _('import to the patch queue (MQ)'))], _('thg import [OPTION] [SOURCE]...')) def import_(ui, repoagent, *pats, **opts): """import an ordered set of patches""" from tortoisehg.hgqt import thgimport dlg = thgimport.ImportDialog(repoagent, None, **opts) dlg.setfilepaths(pats) return dlg @command('^init', [], _('thg init [DEST]')) def init(ui, dest='.', **opts): """init dialog""" from tortoisehg.hgqt import hginit dlg = hginit.InitDialog(ui, hglib.tounicode(dest)) dlg.newRepository.connect(qtrun.openRepoInWorkbench) return dlg @command('^lock|unlock', [], _('thg lock')) def lock(ui, repoagent, **opts): """lock dialog""" from tortoisehg.hgqt import locktool return locktool.LockDialog(repoagent) @command('^log|history|explorer|workbench', [('k', 'query', '', _('search for a given text or revset')), ('r', 'rev', '', _('select the specified revision')), ('l', 'limit', '', _('(DEPRECATED)')), ('', 'newworkbench', None, _('open a new workbench window'))], _('thg log [OPTIONS] [FILE]')) def log(ui, *pats, **opts): """workbench application""" if opts.get('query') and pats: # 'filelog' does not support -k, and multiple filenames are packed # into revset query that may conflict with user-supplied one. raise error.Abort(_('cannot specify both -k/--query and filenames')) root = opts.get('root') or paths.find_root() if root and len(pats) == 1 and os.path.isfile(pats[0]): # TODO: do not instantiate repo here repo = thgrepo.repository(ui, root) repoagent = repo._pyqtobj return filelog(ui, repoagent, *pats, **opts) # Before starting the workbench, we must check if we must try to reuse an # existing workbench window (we don't by default) # Note that if the "single workbench mode" is enabled, and there is no # existing workbench window, we must tell the Workbench object to create # the workbench server singleworkbenchmode = ui.configbool('tortoisehg', 'workbench.single', True) mustcreateserver = False if singleworkbenchmode: newworkbench = opts.get('newworkbench') if root and not newworkbench: # TODO: send -rREV to server q = opts.get('query') or _formatfilerevset(pats) if qtapp.connectToExistingWorkbench(root, q): # The were able to connect to an existing workbench server, and # it confirmed that it has opened the selected repo for us sys.exit(0) # there is no pre-existing workbench server serverexists = False else: serverexists = qtapp.connectToExistingWorkbench('[echo]') # When in " single workbench mode", we must create a server if there # is not one already mustcreateserver = not serverexists w = _workbench(ui, *pats, **opts) if mustcreateserver: qtrun.createWorkbenchServer() return w @command('manifest', [('r', 'rev', '', _('revision to display')), ('n', 'line', '', _('open to line')), ('p', 'pattern', '', _('initial search pattern'))], _('thg manifest [-r REV] [FILE]')) def manifest(ui, repoagent, *pats, **opts): """display the current or given revision of the project manifest""" from tortoisehg.hgqt import revdetails as revdetailsmod repo = repoagent.rawRepo() rev = scmutil.revsingle(repo, opts.get('rev')).rev() dlg = revdetailsmod.createManifestDialog(repoagent, rev) if pats: path = hglib.canonpaths(pats)[0] dlg.setFilePath(hglib.tounicode(path)) if opts.get('line'): try: lineno = int(opts['line']) except ValueError: raise error.Abort(_('invalid line number: %s') % opts['line']) dlg.showLine(lineno) if opts.get('pattern'): dlg.setSearchPattern(hglib.tounicode(opts['pattern'])) return dlg @command('^merge', [('r', 'rev', '', _('revision to merge'))], _('thg merge [[-r] REV]')) def merge(ui, repoagent, *pats, **opts): """merge wizard""" from tortoisehg.hgqt import merge as mergemod rev = opts.get('rev') or None if not rev and len(pats): rev = pats[0] if not rev: raise error.Abort(_('Merge revision not specified or not found')) repo = repoagent.rawRepo() rev = scmutil.revsingle(repo, rev).rev() return mergemod.MergeDialog(repoagent, rev) @command('postreview', [('r', 'rev', [], _('a revision to post'))], _('thg postreview [-r] REV...')) def postreview(ui, repoagent, *pats, **opts): """post changesets to reviewboard""" from tortoisehg.hgqt import postreview as postreviewmod repo = repoagent.rawRepo() if 'reviewboard' not in repo.extensions(): url = 'https://www.mercurial-scm.org/wiki/ReviewboardExtension' raise error.Abort(_('reviewboard extension not enabled'), hint=(_('see %(url)s') % {'url': url})) revs = opts.get('rev') or None if not revs and len(pats): revs = pats[0] if not revs: raise error.Abort(_('no revisions specified')) return postreviewmod.PostReviewDialog(repo.ui, repoagent, revs) @command('^prune|obsolete|kill', [('r', 'rev', [], _('revisions to prune'))], _('thg prune [-r] REV...')) def prune(ui, repoagent, *revs, **opts): """hide changesets by marking them obsolete""" from tortoisehg.hgqt import prune as prunemod revs = list(revs) revs.extend(opts.get('rev')) if len(revs) < 2: revspec = ''.join(revs) else: revspec = hglib.formatrevspec('%lr', revs) return prunemod.createPruneDialog(repoagent, hglib.tounicode(revspec)) @command('^purge', [], _('thg purge')) def purge(ui, repoagent, *pats, **opts): """purge unknown and/or ignore files from repository""" from tortoisehg.hgqt import purge as purgemod return purgemod.PurgeDialog(repoagent) @command('^rebase', [('', 'keep', False, _('keep original changesets')), ('', 'keepbranches', False, _('keep original branch names')), ('', 'detach', False, _('(DEPRECATED)')), ('s', 'source', '', _('rebase from the specified changeset')), ('d', 'dest', '', _('rebase onto the specified changeset'))], _('thg rebase -s REV -d REV [--keep]')) def rebase(ui, repoagent, *pats, **opts): """rebase dialog""" from tortoisehg.hgqt import rebase as rebasemod repo = repoagent.rawRepo() if os.path.exists(repo.vfs.join('rebasestate')): # TODO: move info dialog into RebaseDialog if possible qtlib.InfoMsgBox(hglib.tounicode(_('Rebase already in progress')), hglib.tounicode(_('Resuming rebase already in ' 'progress'))) elif not opts['source'] or not opts['dest']: raise error.Abort(_('You must provide source and dest arguments')) return rebasemod.RebaseDialog(repoagent, None, **opts) @command('rejects', [], _('thg rejects [FILE]')) def rejects(ui, *pats, **opts): """manually resolve rejected patch chunks""" from tortoisehg.hgqt import rejects as rejectsmod if len(pats) != 1: raise error.Abort(_('You must provide the path to a file')) path = pats[0] if path.endswith('.rej'): path = path[:-4] return rejectsmod.RejectsDialog(ui, path) @command('remove|rm', [], _('thg remove [FILE]...')) def remove(ui, repoagent, *pats, **opts): """remove selected files""" return quickop.run(ui, repoagent, *pats, **opts) @command('rename|mv|copy', [], _('thg rename [SOURCE] [DEST]')) def rename(ui, repoagent, source=None, dest=None, **opts): """rename dialog""" from tortoisehg.hgqt import rename as renamemod repo = repoagent.rawRepo() cwd = repo.getcwd() if source: source = hglib.tounicode(pathutil.canonpath(repo.root, cwd, source)) if dest: dest = hglib.tounicode(pathutil.canonpath(repo.root, cwd, dest)) iscopy = (opts.get('alias') == 'copy') return renamemod.RenameDialog(repoagent, None, source, dest, iscopy) @command('^repoconfig', [('', 'focus', '', _('field to give initial focus'))], _('thg repoconfig')) def repoconfig(ui, repoagent, *pats, **opts): """repository configuration editor""" from tortoisehg.hgqt import settings return settings.SettingsDialog(True, focus=opts.get('focus')) @command('resolve', [], _('thg resolve')) def resolve(ui, repoagent, *pats, **opts): """resolve dialog""" from tortoisehg.hgqt import resolve as resolvemod return resolvemod.ResolveDialog(repoagent) @command('^revdetails', [('r', 'rev', '', _('the revision to show'))], _('thg revdetails [-r REV]')) def revdetails(ui, repoagent, *pats, **opts): """revision details tool""" from tortoisehg.hgqt import revdetails as revdetailsmod repo = repoagent.rawRepo() os.chdir(repo.root) rev = opts.get('rev', '.') return revdetailsmod.RevDetailsDialog(repoagent, rev=rev) @command('revert', [], _('thg revert [FILE]...')) def revert(ui, repoagent, *pats, **opts): """revert selected files""" return quickop.run(ui, repoagent, *pats, **opts) @command('rupdate', [('r', 'rev', '', _('revision to update'))], _('thg rupdate [[-r] REV]')) def rupdate(ui, repoagent, *pats, **opts): """update a remote repository""" from tortoisehg.hgqt import rupdate as rupdatemod rev = None if opts.get('rev'): rev = opts.get('rev') elif len(pats) == 1: rev = pats[0] return rupdatemod.createRemoteUpdateDialog(repoagent, rev) @command('^serve', [('', 'web-conf', '', _('name of the hgweb config file (serve more than ' 'one repository)')), ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'))], _('thg serve [--web-conf FILE]')) def serve(ui, *pats, **opts): """start stand-alone webserver""" from tortoisehg.hgqt import serve as servemod return servemod.run(ui, *pats, **opts) if os.name == 'nt': # TODO: extra detection to determine if shell extension is installed @command('shellconfig', [], _('thg shellconfig')) def shellconfig(ui, *pats, **opts): """explorer extension configuration editor""" from tortoisehg.hgqt import shellconf return shellconf.ShellConfigWindow() @command('shelve|unshelve', [], _('thg shelve')) def shelve(ui, repoagent, *pats, **opts): """move changes between working directory and patches""" from tortoisehg.hgqt import shelve as shelvemod return shelvemod.ShelveDialog(repoagent) @command('^sign', [('f', 'force', None, _('sign even if the sigfile is modified')), ('l', 'local', None, _('make the signature local')), ('k', 'key', '', _('the key id to sign with')), ('', 'no-commit', None, _('do not commit the sigfile after signing')), ('m', 'message', '', _('use as commit message'))], _('thg sign [-f] [-l] [-k KEY] [-m TEXT] [REV]')) def sign(ui, repoagent, *pats, **opts): """sign tool""" from tortoisehg.hgqt import sign as signmod repo = repoagent.rawRepo() if 'gpg' not in repo.extensions(): raise error.Abort(_('Please enable the Gpg extension first.')) kargs = {} rev = len(pats) > 0 and pats[0] or None if rev: kargs['rev'] = rev return signmod.SignDialog(repoagent, opts=opts, **kargs) @command('^status|st', [('c', 'clean', False, _('show files without changes')), ('i', 'ignored', False, _('show ignored files'))], _('thg status [OPTIONS] [FILE]')) def status(ui, repoagent, *pats, **opts): """browse working copy status""" from tortoisehg.hgqt import status as statusmod repo = repoagent.rawRepo() pats = hglib.canonpaths(pats) os.chdir(repo.root) return statusmod.StatusDialog(repoagent, pats, opts) @command('^strip', [('f', 'force', None, _('discard uncommitted changes (no backup)')), ('n', 'nobackup', None, _('do not back up stripped revisions')), ('k', 'keep', None, _('do not modify working copy during strip')), ('r', 'rev', '', _('revision to strip'))], _('thg strip [-k] [-f] [-n] [[-r] REV]')) def strip(ui, repoagent, *pats, **opts): """strip dialog""" from tortoisehg.hgqt import thgstrip rev = None if opts.get('rev'): rev = opts.get('rev') elif len(pats) == 1: rev = pats[0] return thgstrip.createStripDialog(repoagent, rev=rev, opts=opts) @command('^sync|synchronize', [('B', 'bookmarks', False, _('open the bookmark sync window'))], _('thg sync [OPTION]... [PEER]')) def sync(ui, repoagent, url=None, **opts): """synchronize with other repositories""" from tortoisehg.hgqt import bookmark as bookmarkmod, repowidget url = hglib.tounicode(url) if opts.get('bookmarks'): return bookmarkmod.SyncBookmarkDialog(repoagent, url) repo = repoagent.rawRepo() repo.ui.setconfig('tortoisehg', 'defaultwidget', 'sync') w = repowidget.LightRepoWindow(repoagent) if url: w.setSyncUrl(url) return w @command('^tag', [('f', 'force', None, _('replace existing tag')), ('l', 'local', None, _('make the tag local')), ('r', 'rev', '', _('revision to tag')), ('', 'remove', None, _('remove a tag')), ('m', 'message', '', _('use as commit message'))], _('thg tag [-f] [-l] [-m TEXT] [-r REV] [NAME]')) def tag(ui, repoagent, *pats, **opts): """tag tool""" from tortoisehg.hgqt import tag as tagmod kargs = {} tag = len(pats) > 0 and pats[0] or None if tag: kargs['tag'] = tag rev = opts.get('rev') if rev: kargs['rev'] = rev return tagmod.TagDialog(repoagent, opts=opts, **kargs) @command('thgstatus', [('', 'delay', None, _('wait until the second ticks over')), ('n', 'notify', [], _('notify the shell for paths given')), ('', 'remove', None, _('remove the status cache')), ('s', 'show', None, _('show the contents of the status cache ' '(no update)')), ('', 'all', None, _('update all repos in current dir'))], _('thg thgstatus [OPTION]')) def thgstatus(ui, *pats, **opts): """update TortoiseHg status cache""" from tortoisehg.util import thgstatus as thgstatusmod thgstatusmod.run(ui, *pats, **opts) @command('topics', [('r', 'rev', '', _('revision'))], _('thg topics [-r REV] [NAME]')) def topics(ui, repoagent, *names, **opts): """add, remove or change a topic""" repo = repoagent.rawRepo() if 'topic' not in repo.extensions(): raise error.Abort(_('Please enable the Topic extension first.')) from tortoisehg.hgqt import topics as topicsmod rev = scmutil.revsingle(repo, opts.get('rev')).rev() if len(names) > 1: raise error.Abort(_('only one new topic name allowed')) dlg = topicsmod.TopicsDialog(repoagent, rev) if names: dlg.setTopicName(hglib.tounicode(names[0])) return dlg @command('^update|checkout|co', [('C', 'clean', None, _('discard uncommitted changes (no backup)')), ('r', 'rev', '', _('revision to update')),], _('thg update [-C] [[-r] REV]')) def update(ui, repoagent, *pats, **opts): """update/checkout tool""" from tortoisehg.hgqt import update as updatemod rev = None if opts.get('rev'): rev = opts.get('rev') elif len(pats) == 1: rev = pats[0] return updatemod.UpdateDialog(repoagent, rev, None, opts) @command('^userconfig', [('', 'focus', '', _('field to give initial focus'))], _('thg userconfig')) def userconfig(ui, *pats, **opts): """user configuration editor""" from tortoisehg.hgqt import settings return settings.SettingsDialog(False, focus=opts.get('focus')) @command('^vdiff', [('c', 'change', '', _('changeset to view in diff tool')), ('r', 'rev', [], _('revisions to view in diff tool')), ('b', 'bundle', '', _('bundle file to preview'))], _('launch visual diff tool')) def vdiff(ui, repoagent, *pats, **opts): """launch configured visual diff tool""" from tortoisehg.hgqt import visdiff repo = repoagent.rawRepo() if opts.get('bundle'): repo = thgrepo.repository(ui, opts.get('bundle')) pats = hglib.canonpaths(pats) return visdiff.visualdiff(ui, repo, pats, opts) @command('^version', [('v', 'verbose', None, _('print license'))], _('thg version [OPTION]')) def version(ui, **opts): """output version and copyright information""" ui.write(_('TortoiseHg Dialogs (version %s), ' 'Mercurial (version %s)\n') % (thgversion.version(), hglib.hgversion)) if not ui.quiet: ui.write(shortlicense)