1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
 
3
# This program is free software; you can redistribute it and/or modify
 
 
4
# it under the terms of the GNU General Public License as published by
 
 
5
# the Free Software Foundation; either version 2 of the License, or
 
 
6
# (at your option) any later version.
 
 
8
# This program is distributed in the hope that it will be useful,
 
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
11
# GNU General Public License for more details.
 
 
13
# You should have received a copy of the GNU General Public License
 
 
14
# along with this program; if not, write to the Free Software
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
18
# TODO: probably should say which arguments are candidates for glob
 
 
19
# expansion on windows and do that at the command level.
 
 
21
# TODO: Help messages for options.
 
 
23
# TODO: Define arguments by objects, rather than just using names.
 
 
24
# Those objects can specify the expected type of the argument, which
 
 
25
# would help with validation and shell completion.
 
 
33
from bzrlib.trace import mutter, note, log_error, warning
 
 
34
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
 
 
35
from bzrlib.branch import find_branch
 
 
36
from bzrlib import BZRDIR
 
 
42
def register_command(cmd):
 
 
43
    "Utility function to help register a command"
 
 
46
    if k.startswith("cmd_"):
 
 
47
        k_unsquished = _unsquish_command_name(k)
 
 
50
    if not plugin_cmds.has_key(k_unsquished):
 
 
51
        plugin_cmds[k_unsquished] = cmd
 
 
52
        mutter('registered plugin command %s', k_unsquished)      
 
 
54
        log_error('Two plugins defined the same command: %r' % k)
 
 
55
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
 
58
def _squish_command_name(cmd):
 
 
59
    return 'cmd_' + cmd.replace('-', '_')
 
 
62
def _unsquish_command_name(cmd):
 
 
63
    assert cmd.startswith("cmd_")
 
 
64
    return cmd[4:].replace('_','-')
 
 
67
def _parse_revision_str(revstr):
 
 
68
    """This handles a revision string -> revno.
 
 
70
    This always returns a list.  The list will have one element for 
 
 
72
    It supports integers directly, but everything else it
 
 
73
    defers for passing to Branch.get_revision_info()
 
 
75
    >>> _parse_revision_str('234')
 
 
77
    >>> _parse_revision_str('234..567')
 
 
79
    >>> _parse_revision_str('..')
 
 
81
    >>> _parse_revision_str('..234')
 
 
83
    >>> _parse_revision_str('234..')
 
 
85
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
 
 
87
    >>> _parse_revision_str('234....789') # Error?
 
 
89
    >>> _parse_revision_str('revid:test@other.com-234234')
 
 
90
    ['revid:test@other.com-234234']
 
 
91
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
 
 
92
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
 
 
93
    >>> _parse_revision_str('revid:test@other.com-234234..23')
 
 
94
    ['revid:test@other.com-234234', 23]
 
 
95
    >>> _parse_revision_str('date:2005-04-12')
 
 
97
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
 
 
98
    ['date:2005-04-12 12:24:33']
 
 
99
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
 
 
100
    ['date:2005-04-12T12:24:33']
 
 
101
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
 
 
102
    ['date:2005-04-12,12:24:33']
 
 
103
    >>> _parse_revision_str('-5..23')
 
 
105
    >>> _parse_revision_str('-5')
 
 
107
    >>> _parse_revision_str('123a')
 
 
109
    >>> _parse_revision_str('abc')
 
 
113
    old_format_re = re.compile('\d*:\d*')
 
 
114
    m = old_format_re.match(revstr)
 
 
116
        warning('Colon separator for revision numbers is deprecated.'
 
 
119
        for rev in revstr.split(':'):
 
 
121
                revs.append(int(rev))
 
 
126
    for x in revstr.split('..'):
 
 
137
def get_merge_type(typestring):
 
 
138
    """Attempt to find the merge class/factory associated with a string."""
 
 
139
    from merge import merge_types
 
 
141
        return merge_types[typestring][0]
 
 
143
        templ = '%s%%7s: %%s' % (' '*12)
 
 
144
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
 
145
        type_list = '\n'.join(lines)
 
 
146
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
 
147
            (typestring, type_list)
 
 
148
        raise BzrCommandError(msg)
 
 
151
def get_merge_type(typestring):
 
 
152
    """Attempt to find the merge class/factory associated with a string."""
 
 
153
    from merge import merge_types
 
 
155
        return merge_types[typestring][0]
 
 
157
        templ = '%s%%7s: %%s' % (' '*12)
 
 
158
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
 
159
        type_list = '\n'.join(lines)
 
 
160
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
 
161
            (typestring, type_list)
 
 
162
        raise BzrCommandError(msg)
 
 
166
def _get_cmd_dict(plugins_override=True):
 
 
167
    import bzrlib.builtins
 
 
170
    builtins = bzrlib.builtins.__dict__
 
 
171
    for name in builtins:
 
 
172
        if name.startswith("cmd_"):
 
 
173
            d[_unsquish_command_name(name)] = builtins[name]
 
 
174
    # If we didn't load plugins, the plugin_cmds dict will be empty
 
 
176
        d.update(plugin_cmds)
 
 
178
        d2 = plugin_cmds.copy()
 
 
184
def get_all_cmds(plugins_override=True):
 
 
185
    """Return canonical name and class for all registered commands."""
 
 
186
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
 
190
def get_cmd_class(cmd, plugins_override=True):
 
 
191
    """Return the canonical name and command class for a command.
 
 
193
    cmd = str(cmd)                      # not unicode
 
 
195
    # first look up this command under the specified name
 
 
196
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
 
197
    mutter("all commands: %r", cmds.keys())
 
 
199
        return cmd, cmds[cmd]
 
 
203
    # look for any command which claims this as an alias
 
 
204
    for cmdname, cmdclass in cmds.iteritems():
 
 
205
        if cmd in cmdclass.aliases:
 
 
206
            return cmdname, cmdclass
 
 
208
    cmdclass = ExternalCommand.find_command(cmd)
 
 
212
    raise BzrCommandError("unknown command %r" % cmd)
 
 
215
class Command(object):
 
 
216
    """Base class for commands.
 
 
218
    The docstring for an actual command should give a single-line
 
 
219
    summary, then a complete description of the command.  A grammar
 
 
220
    description will be inserted.
 
 
223
        List of argument forms, marked with whether they are optional,
 
 
227
        List of options that may be given for this command.
 
 
230
        If true, this command isn't advertised.
 
 
239
    def __init__(self, options, arguments):
 
 
240
        """Construct and run the command.
 
 
242
        Sets self.status to the return value of run()."""
 
 
243
        assert isinstance(options, dict)
 
 
244
        assert isinstance(arguments, dict)
 
 
245
        cmdargs = options.copy()
 
 
246
        cmdargs.update(arguments)
 
 
247
        if self.__doc__ == Command.__doc__:
 
 
248
            from warnings import warn
 
 
249
            warn("No help message set for %r" % self)
 
 
250
        self.status = self.run(**cmdargs)
 
 
251
        if self.status is None:
 
 
255
    def run(self, *args, **kwargs):
 
 
256
        """Override this in sub-classes.
 
 
258
        This is invoked with the options and arguments bound to
 
 
261
        Return 0 or None if the command was successful, or a shell
 
 
264
        raise NotImplementedError()
 
 
267
class ExternalCommand(Command):
 
 
268
    """Class to wrap external commands.
 
 
270
    We cheat a little here, when get_cmd_class() calls us we actually
 
 
271
    give it back an object we construct that has the appropriate path,
 
 
272
    help, options etc for the specified command.
 
 
274
    When run_bzr() tries to instantiate that 'class' it gets caught by
 
 
275
    the __call__ method, which we override to call the Command.__init__
 
 
276
    method. That then calls our run method which is pretty straight
 
 
279
    The only wrinkle is that we have to map bzr's dictionary of options
 
 
280
    and arguments back into command line options and arguments for the
 
 
284
    def find_command(cls, cmd):
 
 
286
        bzrpath = os.environ.get('BZRPATH', '')
 
 
288
        for dir in bzrpath.split(os.pathsep):
 
 
289
            path = os.path.join(dir, cmd)
 
 
290
            if os.path.isfile(path):
 
 
291
                return ExternalCommand(path)
 
 
295
    find_command = classmethod(find_command)
 
 
297
    def __init__(self, path):
 
 
300
        pipe = os.popen('%s --bzr-usage' % path, 'r')
 
 
301
        self.takes_options = pipe.readline().split()
 
 
303
        for opt in self.takes_options:
 
 
304
            if not opt in OPTIONS:
 
 
305
                raise BzrError("Unknown option '%s' returned by external command %s"
 
 
308
        # TODO: Is there any way to check takes_args is valid here?
 
 
309
        self.takes_args = pipe.readline().split()
 
 
311
        if pipe.close() is not None:
 
 
312
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
 
 
314
        pipe = os.popen('%s --bzr-help' % path, 'r')
 
 
315
        self.__doc__ = pipe.read()
 
 
316
        if pipe.close() is not None:
 
 
317
            raise BzrError("Failed funning '%s --bzr-help'" % path)
 
 
319
    def __call__(self, options, arguments):
 
 
320
        Command.__init__(self, options, arguments)
 
 
323
    def run(self, **kargs):
 
 
330
            optname = name.replace('_','-')
 
 
332
            if OPTIONS.has_key(optname):
 
 
334
                opts.append('--%s' % optname)
 
 
335
                if value is not None and value is not True:
 
 
336
                    opts.append(str(value))
 
 
338
                # it's an arg, or arg list
 
 
339
                if type(value) is not list:
 
 
345
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
 
 
350
def parse_spec(spec):
 
 
356
    >>> parse_spec("../@")
 
 
358
    >>> parse_spec("../f/@35")
 
 
360
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
 
361
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
 
 
366
        parsed = spec.split('/@')
 
 
367
        assert len(parsed) == 2
 
 
372
                parsed[1] = int(parsed[1])
 
 
374
                pass # We can allow stuff like ./@revid:blahblahblah
 
 
378
        parsed = [spec, None]
 
 
384
# list of all available options; the rhs can be either None for an
 
 
385
# option that takes no argument, or a constructor function that checks
 
 
398
    'revision':               _parse_revision_str,
 
 
410
    'merge-type':             get_merge_type,
 
 
424
def parse_args(argv):
 
 
425
    """Parse command line.
 
 
427
    Arguments and options are parsed at this level before being passed
 
 
428
    down to specific command handlers.  This routine knows, from a
 
 
429
    lookup table, something about the available options, what optargs
 
 
430
    they take, and which commands will accept them.
 
 
432
    >>> parse_args('--help'.split())
 
 
434
    >>> parse_args('help -- --invalidcmd'.split())
 
 
435
    (['help', '--invalidcmd'], {})
 
 
436
    >>> parse_args('--version'.split())
 
 
437
    ([], {'version': True})
 
 
438
    >>> parse_args('status --all'.split())
 
 
439
    (['status'], {'all': True})
 
 
440
    >>> parse_args('commit --message=biter'.split())
 
 
441
    (['commit'], {'message': u'biter'})
 
 
442
    >>> parse_args('log -r 500'.split())
 
 
443
    (['log'], {'revision': [500]})
 
 
444
    >>> parse_args('log -r500..600'.split())
 
 
445
    (['log'], {'revision': [500, 600]})
 
 
446
    >>> parse_args('log -vr500..600'.split())
 
 
447
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
 
448
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
 
449
    (['log'], {'revision': ['v500', 600]})
 
 
457
        if not argsover and a[0] == '-':
 
 
458
            # option names must not be unicode
 
 
463
                    # We've received a standalone -- No more flags
 
 
466
                mutter("  got option %r" % a)
 
 
468
                    optname, optarg = a[2:].split('=', 1)
 
 
471
                if optname not in OPTIONS:
 
 
472
                    raise BzrError('unknown long option %r' % a)
 
 
475
                if shortopt in SHORT_OPTIONS:
 
 
476
                    # Multi-character options must have a space to delimit
 
 
478
                    optname = SHORT_OPTIONS[shortopt]
 
 
480
                    # Single character short options, can be chained,
 
 
481
                    # and have their value appended to their name
 
 
483
                    if shortopt not in SHORT_OPTIONS:
 
 
484
                        # We didn't find the multi-character name, and we
 
 
485
                        # didn't find the single char name
 
 
486
                        raise BzrError('unknown short option %r' % a)
 
 
487
                    optname = SHORT_OPTIONS[shortopt]
 
 
490
                        # There are extra things on this option
 
 
491
                        # see if it is the value, or if it is another
 
 
493
                        optargfn = OPTIONS[optname]
 
 
495
                            # This option does not take an argument, so the
 
 
496
                            # next entry is another short option, pack it back
 
 
498
                            argv.insert(0, '-' + a[2:])
 
 
500
                            # This option takes an argument, so pack it
 
 
505
                # XXX: Do we ever want to support this, e.g. for -r?
 
 
506
                raise BzrError('repeated option %r' % a)
 
 
508
            optargfn = OPTIONS[optname]
 
 
512
                        raise BzrError('option %r needs an argument' % a)
 
 
515
                opts[optname] = optargfn(optarg)
 
 
518
                    raise BzrError('option %r takes no argument' % optname)
 
 
528
def _match_argform(cmd, takes_args, args):
 
 
531
    # step through args and takes_args, allowing appropriate 0-many matches
 
 
532
    for ap in takes_args:
 
 
536
                argdict[argname] = args.pop(0)
 
 
537
        elif ap[-1] == '*': # all remaining arguments
 
 
539
                argdict[argname + '_list'] = args[:]
 
 
542
                argdict[argname + '_list'] = None
 
 
545
                raise BzrCommandError("command %r needs one or more %s"
 
 
546
                        % (cmd, argname.upper()))
 
 
548
                argdict[argname + '_list'] = args[:]
 
 
550
        elif ap[-1] == '$': # all but one
 
 
552
                raise BzrCommandError("command %r needs one or more %s"
 
 
553
                        % (cmd, argname.upper()))
 
 
554
            argdict[argname + '_list'] = args[:-1]
 
 
560
                raise BzrCommandError("command %r requires argument %s"
 
 
561
                        % (cmd, argname.upper()))
 
 
563
                argdict[argname] = args.pop(0)
 
 
566
        raise BzrCommandError("extra argument to command %s: %s"
 
 
574
    """Execute a command.
 
 
576
    This is similar to main(), but without all the trappings for
 
 
577
    logging and error handling.  
 
 
580
       The command-line arguments, without the program name from argv[0]
 
 
582
    Returns a command status or raises an exception.
 
 
584
    Special master options: these must come before the command because
 
 
585
    they control how the command is interpreted.
 
 
588
        Do not load plugin modules at all
 
 
591
        Only use builtin commands.  (Plugins are still allowed to change
 
 
595
        Run under the Python profiler.
 
 
598
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
 
600
    opt_profile = opt_no_plugins = opt_builtin = False
 
 
602
    # --no-plugins is handled specially at a very early stage. We need
 
 
603
    # to load plugins before doing other command parsing so that they
 
 
604
    # can override commands, but this needs to happen first.
 
 
609
        elif a == '--no-plugins':
 
 
610
            opt_no_plugins = True
 
 
611
        elif a == '--builtin':
 
 
617
    if not opt_no_plugins:
 
 
618
        from bzrlib.plugin import load_plugins
 
 
621
    args, opts = parse_args(argv)
 
 
624
        from bzrlib.help import help
 
 
631
    if 'version' in opts:
 
 
632
        from bzrlib.builtins import show_version
 
 
637
        from bzrlib.help import help
 
 
641
    cmd = str(args.pop(0))
 
 
643
    canonical_cmd, cmd_class = \
 
 
644
                   get_cmd_class(cmd, plugins_override=not opt_builtin)
 
 
646
    # check options are reasonable
 
 
647
    allowed = cmd_class.takes_options
 
 
649
        if oname not in allowed:
 
 
650
            raise BzrCommandError("option '--%s' is not allowed for command %r"
 
 
653
    # mix arguments and options into one dictionary
 
 
654
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
 
656
    for k, v in opts.items():
 
 
657
        cmdopts[k.replace('-', '_')] = v
 
 
660
        import hotshot, tempfile
 
 
661
        pffileno, pfname = tempfile.mkstemp()
 
 
663
            prof = hotshot.Profile(pfname)
 
 
664
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
 
668
            stats = hotshot.stats.load(pfname)
 
 
670
            stats.sort_stats('time')
 
 
671
            ## XXX: Might like to write to stderr or the trace file instead but
 
 
672
            ## print_stats seems hardcoded to stdout
 
 
673
            stats.print_stats(20)
 
 
681
        return cmd_class(cmdopts, cmdargs).status 
 
 
686
    bzrlib.trace.log_startup(argv)
 
 
687
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
 
691
            return run_bzr(argv[1:])
 
 
693
            # do this here inside the exception wrappers to catch EPIPE
 
 
695
    except BzrCommandError, e:
 
 
696
        # command line syntax error, etc
 
 
700
        bzrlib.trace.log_exception()
 
 
702
    except AssertionError, e:
 
 
703
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
 
 
705
    except KeyboardInterrupt, e:
 
 
706
        bzrlib.trace.note('interrupted')
 
 
710
        if (isinstance(e, IOError) 
 
 
711
            and hasattr(e, 'errno')
 
 
712
            and e.errno == errno.EPIPE):
 
 
713
            bzrlib.trace.note('broken pipe')
 
 
716
            bzrlib.trace.log_exception()
 
 
720
if __name__ == '__main__':
 
 
721
    sys.exit(main(sys.argv))