repoman / repoman / dispatch.py

# Copyright 2009-2012 Edlund A/S
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
'''
Command dispatch handling.
'''

import sys
import os
import getopt
import pdb
import urllib2
import datetime

from mercurial import fancyopts
from mercurial.util import checksignature
from mercurial.cmdutil import findrepo as _findrepo
from mercurial.dispatch import _parseconfig, _earlygetopt
import mercurial.extensions
import mercurial.hg
import hgext.color

from repoman import ui
from repoman import forest
from repoman import util
from repoman import commands
from repoman import cmdutil
from repoman import extensions

blocking_dependencies = {
        'merge': ['branch', 'push', 'pull', 'rebase', 'uncommit'],
        }

def colorcmd(ui, opts):
    if ui.plain():
        return
    mode = opts.get('color', ui.config('color', 'mode', 'auto'))
    if not mode:
        mode = ui.config('color', 'mode', 'auto')
    omode = mode
    if mode == 'auto' or mode == 'always':
        if os.name == 'nt' and 'TERM' not in os.environ:
            # looks line a cmd.exe console, use win32 API or nothing
            mode = hgext.color.w32effects and 'win32' or 'none'
        else:
            mode = 'ansi'
    if mode == 'win32':
        if hgext.color.w32effects is None:
            # only warn if color.mode is explicitly set to win32
            ui.warn(_('win32console not found, please install pywin32\n'))
            return
        hgext.color._effects.update(hgext.color.w32effects)
    elif mode != 'ansi':
        return
    if (omode == 'always' or
        (omode == 'auto' and (os.environ.get('TERM') != 'dumb'
                                      and ui.formatted()))):
        hgext.color.colorui._colormode = mode
        if not issubclass(ui.__class__, hgext.color.colorui):
            hgext.color.colorui.__bases__ = (ui.__class__,)
            ui.__class__ = hgext.color.colorui
        hgext.color.extstyles()
        hgext.color.configstyles(ui)

def is_blocked(ui, cmd, f=None, **options):
    if f is None:
        f = forest.Forest(ui, **options)
    if f.state is None:
        return None

    blocked = blocking_dependencies.get(f.state.name)
    if cmd in blocked:
        return f.state.name
    return None

def load_extensions(myui):
    # load required hg extensions
    for ext in ['mq', 'rebase']:
        myui.setconfig('extensions', ext, '')
    mercurial.extensions.loadall(myui)
    # load repoman extensions
    extensions.loadall(myui)
    cmd_dict = dict(commands.commands)
    for name, ext in extensions.extensions():
        cmd_dict.update(ext.cmdtable)
    return cmd_dict

def _handle_exc(myui, options, err_text, extra_text=None):
    if myui:
        err_output = lambda x: myui.write_err(x, label='repoman.error')
        output = myui.write_stderr if myui else sys.stderr.write
    else:
        err_output = sys.stderr.write
        output = sys.stderr.write

    err_output(err_text)
    if extra_text:
        output(extra_text)

    if '--debugger' in sys.argv:
        pdb.post_mortem(sys.exc_info()[2])
    if myui is not None:
        myui.traceback()
    sys.exit(1)

def run(argv):
    try:
        with open(os.path.expanduser(os.path.join('~', '.repolog')), 'a') as fh:
            fh.write('%s: %s: %s\n' % (datetime.datetime.utcnow().isoformat(), os.getcwd(), argv))
    except IOError:
        pass # ignore logging if writing to the file fails

    if '--version' in argv:
        argv = ['version']

    repoextpath = [x for x in os.environ.get('REPOPYTHONPATH', '').split(';' if os.name == 'nt' else ':') if x]
    sys.path.extend(repoextpath)

    myui = None
    start_time = None
    options = None
    try:
        if '--simpleui' in argv:
            ui.ui = ui.SimpleUi

        myui = ui.ui()
        configopts = _earlygetopt(['--config'], argv)
        coloropts = _earlygetopt(['--color'], argv)
        coloropts = coloropts[0] if coloropts else ''
        _parseconfig(myui, configopts)
        colorcmd(myui, {'color': coloropts})
        cmd_dict = load_extensions(myui)
        cmd, options, cmdoptions, args = parse_commandline(myui, cmd_dict, argv)
        if options.get('debugger'):
            pdb.set_trace()
        if options.get('time'):
            start_time = util.get_times()
        myui2 = ui.ui(**options)
        myui2.__class__ = myui.__class__
        myui = myui2
        _parseconfig(myui, configopts)
        colorcmd(myui, {'color': coloropts})
        myui.debugflag = bool(options.get('debug'))
        myui.verbose = myui.debugflag or bool(options.get('verbose'))
        myui.quiet = not myui.debugflag and bool(options.get('quiet'))
        myui.silent = not myui.debugflag and bool(options.get('silent'))
        myui._traceback = myui.tracebackflag = bool(options.get('traceback'))
        myui.filter = options.get('filter')
        myui._buffered = options.get('buffered')
        if not cmd:
            raise mercurial.error.UnknownCommand(args)

        if options.get('help'):
            commands.help(myui, cmd)
            return

        cmd_fnc = cmd_dict[cmd][0]

        # Non-forest commands.
        if cmd_fnc.__name__ in commands.nonforest_commands:
            checksignature(cmd_fnc)(myui, *args, **cmdoptions)

        # Module commands.
        elif cmd_fnc.__name__ in commands.module_commands:
            repo_path = options['repository'] or _findrepo(os.getcwd())
            if not repo_path or not os.path.isdir(os.path.join(repo_path, '.hg')):
                raise util.Abort('module %s not found' % repo_path)
            forest_path = os.path.join(repo_path, '..')
            if forest.is_forest(forest_path):
                f = forest.Forest(myui, forest_path=forest_path, **options)
                blocking = is_blocked(myui, cmd_fnc.__name__, f, **options)
                if blocking:
                    raise util.Abort('there is an outstanding %s operation that is blocking %s' % (blocking, cmd_fnc.__name__))
            repo = mercurial.hg.repository(myui, repo_path)
            checksignature(cmd_fnc)(myui, repo, *args, **cmdoptions)

        # Forest commands.
        else:
            forest_path = options['repository'] or forest.find_forest(os.getcwd())
            f = forest.Forest(myui, forest_path=forest_path, **options)
            blocking = is_blocked(myui, cmd_fnc.__name__, f, **options)
            if blocking:
                raise util.Abort('there is an outstanding %s operation that is blocking %s' % (blocking, cmd_fnc.__name__))
            checksignature(cmd_fnc)(myui, f, *args, **cmdoptions)
        sys.exit(0)

    except mercurial.error.AmbiguousCommand, inst:
        _handle_exc(myui, options,
                'ambiguous command\npossible commands: %s\n' %
                ', '.join(sorted([x[0] for x in inst.args[1]])))
    except util.SignatureError:
        _handle_exc(myui, options,
                'repo %s: invalid arguments\n' % (cmd,),
                'usage: repo %s %s\n' % (cmd, cmd_dict[cmd][3]))
    except (util.Abort, urllib2.HTTPError, SystemError, 
            util.UnknownSolutionError,
            util.FormatError,
            util.UnrelatedSolutionError), inst:
        _handle_exc(myui, options,
                'abort: %s\n' % inst)
    except util.ModulesMissingError, inst:
        _handle_exc(myui, options,
                'abort: the following modules are missing: %s\n' %
                (', '.join(sorted(inst.args[0]))))
    except mercurial.error.UnknownCommand, inst:
        _handle_exc(myui, options,
                'unknown command: %s\n' % inst)
    except urllib2.URLError, inst:
        _handle_exc(myui, options,
                'abort: error communicating with remote host: %s\n' %
                inst.reason)
    except mercurial.error.RepoError, inst:
        _handle_exc(myui, options,
                'abort: %s!\n' % inst)

    finally:
        if myui and start_time:
            util.print_times(myui, start_time)
        del myui

def parse_commandline(ui, cmd_dict, cmd_args):
    aliases = cmdutil.build_alias_map(cmd_dict)

    cmd = None
    
    try:
        # Parse global options.
        options = {}
        args = fancyopts.fancyopts(cmd_args, commands.globaloptions, options)
        cmd, args = args[0], args[1:]

        # Find command and parse command options.
        try:
            cmd, cmdinfo = cmdutil.findcmd(cmd, cmd_dict, aliases)
        except mercurial.error.UnknownCommand:
            # command may be defined using --config, return options early
            # handle error elsewhere
            return None, options, None, cmd

        cmdoptions = {}

        # combine global options into command options
        commandinfo = list(cmdinfo[2])
        option_names = [x[1] for x in commandinfo]
        for opt in commands.globaloptions:
            if opt[1] in option_names:
                continue

            commandinfo.append((opt[0], opt[1], options[opt[1]], opt[3]))

        args = fancyopts.fancyopts(args, commandinfo, cmdoptions, True)

        # separate global options back out
        for opt in commands.globaloptions:
            name = opt[1]

            if name in option_names:
                continue

            options[name] = cmdoptions[name]
            del cmdoptions[name]

        return cmd, options, cmdoptions, args

    except IndexError:
        return 'help', {}, {}, []

    except getopt.GetoptError, error:
        err_output = ui.write_err if ui else sys.stderr.write
        output = ui.write_stderr if ui else sys.stderr.write

        old_style = [arg for arg in cmd_args if len(arg) > 3 and arg[0] == '-' and arg[1] != '-']
        if old_style:
            err_output('\nwarning: did you forget to use gnu-style option names?\n')
            err_output('the following options look suspect: %s\n\n' % ', '.join(old_style))
        err_output('abort: %s\n' % error.msg)
        if cmd:
            output('usage: repo %s %s \n' % (cmd, cmd_dict[cmd][3]))
        sys.exit(1)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.