~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-09-13 05:22:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050913052241-52dbd8e8ced620f6
- better BZR_DEBUG trace output

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
# TODO: probably should say which arguments are candidates for glob
19
19
# expansion on windows and do that at the command level.
20
20
 
 
21
# TODO: Help messages for options.
 
22
 
21
23
# TODO: Define arguments by objects, rather than just using names.
22
24
# Those objects can specify the expected type of the argument, which
23
 
# would help with validation and shell completion.  They could also provide
24
 
# help/explanation for that argument in a structured way.
25
 
 
26
 
# TODO: Specific "examples" property on commands for consistent formatting.
27
 
 
28
 
# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
29
 
# the profile output behind so it can be interactively examined?
30
 
 
 
25
# would help with validation and shell completion.
 
26
 
 
27
 
 
28
 
 
29
import sys
31
30
import os
32
 
import sys
33
 
 
34
 
from bzrlib.lazy_import import lazy_import
35
 
lazy_import(globals(), """
36
 
import codecs
37
 
import errno
38
31
from warnings import warn
 
32
from inspect import getdoc
39
33
 
40
34
import bzrlib
41
 
from bzrlib import (
42
 
    debug,
43
 
    errors,
44
 
    option,
45
 
    osutils,
46
 
    trace,
47
 
    )
48
 
""")
49
 
 
50
 
from bzrlib.symbol_versioning import (
51
 
    deprecated_function,
52
 
    deprecated_method,
53
 
    zero_eight,
54
 
    zero_eleven,
55
 
    )
56
 
# Compatibility
57
 
from bzrlib.option import Option
58
 
 
 
35
import bzrlib.trace
 
36
from bzrlib.trace import mutter, note, log_error, warning
 
37
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
 
38
from bzrlib.branch import find_branch
 
39
from bzrlib import BZRDIR
59
40
 
60
41
plugin_cmds = {}
61
42
 
62
43
 
63
 
def register_command(cmd, decorate=False):
64
 
    """Utility function to help register a command
65
 
 
66
 
    :param cmd: Command subclass to register
67
 
    :param decorate: If true, allow overriding an existing command
68
 
        of the same name; the old command is returned by this function.
69
 
        Otherwise it is an error to try to override an existing command.
70
 
    """
 
44
def register_command(cmd):
 
45
    "Utility function to help register a command"
71
46
    global plugin_cmds
72
47
    k = cmd.__name__
73
48
    if k.startswith("cmd_"):
74
49
        k_unsquished = _unsquish_command_name(k)
75
50
    else:
76
51
        k_unsquished = k
77
 
    if k_unsquished not in plugin_cmds:
78
 
        plugin_cmds[k_unsquished] = cmd
79
 
        ## trace.mutter('registered plugin command %s', k_unsquished)
80
 
        if decorate and k_unsquished in builtin_command_names():
81
 
            return _builtin_commands()[k_unsquished]
82
 
    elif decorate:
83
 
        result = plugin_cmds[k_unsquished]
84
 
        plugin_cmds[k_unsquished] = cmd
85
 
        return result
 
52
    if not plugin_cmds.has_key(k_unsquished):
 
53
        plugin_cmds[k_unsquished] = cmd
 
54
        mutter('registered plugin command %s', k_unsquished)      
86
55
    else:
87
 
        trace.log_error('Two plugins defined the same command: %r' % k)
88
 
        trace.log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
56
        log_error('Two plugins defined the same command: %r' % k)
 
57
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
89
58
 
90
59
 
91
60
def _squish_command_name(cmd):
97
66
    return cmd[4:].replace('_','-')
98
67
 
99
68
 
 
69
def _parse_revision_str(revstr):
 
70
    """This handles a revision string -> revno.
 
71
 
 
72
    This always returns a list.  The list will have one element for 
 
73
 
 
74
    It supports integers directly, but everything else it
 
75
    defers for passing to Branch.get_revision_info()
 
76
 
 
77
    >>> _parse_revision_str('234')
 
78
    [234]
 
79
    >>> _parse_revision_str('234..567')
 
80
    [234, 567]
 
81
    >>> _parse_revision_str('..')
 
82
    [None, None]
 
83
    >>> _parse_revision_str('..234')
 
84
    [None, 234]
 
85
    >>> _parse_revision_str('234..')
 
86
    [234, None]
 
87
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
 
88
    [234, 456, 789]
 
89
    >>> _parse_revision_str('234....789') # Error?
 
90
    [234, None, 789]
 
91
    >>> _parse_revision_str('revid:test@other.com-234234')
 
92
    ['revid:test@other.com-234234']
 
93
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
 
94
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
 
95
    >>> _parse_revision_str('revid:test@other.com-234234..23')
 
96
    ['revid:test@other.com-234234', 23]
 
97
    >>> _parse_revision_str('date:2005-04-12')
 
98
    ['date:2005-04-12']
 
99
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
 
100
    ['date:2005-04-12 12:24:33']
 
101
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
 
102
    ['date:2005-04-12T12:24:33']
 
103
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
 
104
    ['date:2005-04-12,12:24:33']
 
105
    >>> _parse_revision_str('-5..23')
 
106
    [-5, 23]
 
107
    >>> _parse_revision_str('-5')
 
108
    [-5]
 
109
    >>> _parse_revision_str('123a')
 
110
    ['123a']
 
111
    >>> _parse_revision_str('abc')
 
112
    ['abc']
 
113
    """
 
114
    import re
 
115
    old_format_re = re.compile('\d*:\d*')
 
116
    m = old_format_re.match(revstr)
 
117
    if m:
 
118
        warning('Colon separator for revision numbers is deprecated.'
 
119
                ' Use .. instead')
 
120
        revs = []
 
121
        for rev in revstr.split(':'):
 
122
            if rev:
 
123
                revs.append(int(rev))
 
124
            else:
 
125
                revs.append(None)
 
126
        return revs
 
127
    revs = []
 
128
    for x in revstr.split('..'):
 
129
        if not x:
 
130
            revs.append(None)
 
131
        else:
 
132
            try:
 
133
                revs.append(int(x))
 
134
            except ValueError:
 
135
                revs.append(x)
 
136
    return revs
 
137
 
 
138
 
 
139
def get_merge_type(typestring):
 
140
    """Attempt to find the merge class/factory associated with a string."""
 
141
    from merge import merge_types
 
142
    try:
 
143
        return merge_types[typestring][0]
 
144
    except KeyError:
 
145
        templ = '%s%%7s: %%s' % (' '*12)
 
146
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
147
        type_list = '\n'.join(lines)
 
148
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
149
            (typestring, type_list)
 
150
        raise BzrCommandError(msg)
 
151
    
 
152
 
100
153
def _builtin_commands():
101
154
    import bzrlib.builtins
102
155
    r = {}
103
156
    builtins = bzrlib.builtins.__dict__
104
157
    for name in builtins:
105
158
        if name.startswith("cmd_"):
106
 
            real_name = _unsquish_command_name(name)
 
159
            real_name = _unsquish_command_name(name)        
107
160
            r[real_name] = builtins[name]
108
161
    return r
 
162
 
109
163
            
110
164
 
111
165
def builtin_command_names():
137
191
    plugins_override
138
192
        If true, plugin commands can override builtins.
139
193
    """
140
 
    try:
141
 
        return _get_cmd_object(cmd_name, plugins_override)
142
 
    except KeyError:
143
 
        raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
144
 
 
145
 
 
146
 
def _get_cmd_object(cmd_name, plugins_override=True):
147
 
    """Worker for get_cmd_object which raises KeyError rather than BzrCommandError."""
148
194
    from bzrlib.externalcommand import ExternalCommand
149
195
 
150
 
    # We want only 'ascii' command names, but the user may have typed
151
 
    # in a Unicode name. In that case, they should just get a
152
 
    # 'command not found' error later.
153
 
    # In the future, we may actually support Unicode command names.
 
196
    cmd_name = str(cmd_name)            # not unicode
154
197
 
155
198
    # first look up this command under the specified name
156
199
    cmds = _get_cmd_dict(plugins_override=plugins_override)
167
210
    cmd_obj = ExternalCommand.find_command(cmd_name)
168
211
    if cmd_obj:
169
212
        return cmd_obj
170
 
    raise KeyError
 
213
 
 
214
    raise BzrCommandError("unknown command %r" % cmd_name)
171
215
 
172
216
 
173
217
class Command(object):
195
239
        List of argument forms, marked with whether they are optional,
196
240
        repeated, etc.
197
241
 
198
 
                Examples:
199
 
 
200
 
                ['to_location', 'from_branch?', 'file*']
201
 
 
202
 
                'to_location' is required
203
 
                'from_branch' is optional
204
 
                'file' can be specified 0 or more times
205
 
 
206
242
    takes_options
207
 
        List of options that may be given for this command.  These can
208
 
        be either strings, referring to globally-defined options,
209
 
        or option objects.  Retrieve through options().
 
243
        List of options that may be given for this command.
210
244
 
211
245
    hidden
212
246
        If true, this command isn't advertised.  This is typically
213
247
        for commands intended for expert users.
214
 
 
215
 
    encoding_type
216
 
        Command objects will get a 'outf' attribute, which has been
217
 
        setup to properly handle encoding of unicode strings.
218
 
        encoding_type determines what will happen when characters cannot
219
 
        be encoded
220
 
            strict - abort if we cannot decode
221
 
            replace - put in a bogus character (typically '?')
222
 
            exact - do not encode sys.stdout
223
 
 
224
 
            NOTE: by default on Windows, sys.stdout is opened as a text
225
 
            stream, therefore LF line-endings are converted to CRLF.
226
 
            When a command uses encoding_type = 'exact', then
227
 
            sys.stdout is forced to be a binary stream, and line-endings
228
 
            will not mangled.
229
 
 
230
248
    """
231
249
    aliases = []
 
250
    
232
251
    takes_args = []
233
252
    takes_options = []
234
 
    encoding_type = 'strict'
235
253
 
236
254
    hidden = False
237
255
    
240
258
        if self.__doc__ == Command.__doc__:
241
259
            warn("No help message set for %r" % self)
242
260
 
243
 
    def _usage(self):
244
 
        """Return single-line grammar for this command.
245
 
 
246
 
        Only describes arguments, not options.
247
 
        """
248
 
        s = 'bzr ' + self.name() + ' '
249
 
        for aname in self.takes_args:
250
 
            aname = aname.upper()
251
 
            if aname[-1] in ['$', '+']:
252
 
                aname = aname[:-1] + '...'
253
 
            elif aname[-1] == '?':
254
 
                aname = '[' + aname[:-1] + ']'
255
 
            elif aname[-1] == '*':
256
 
                aname = '[' + aname[:-1] + '...]'
257
 
            s += aname + ' '
258
 
                
259
 
        assert s[-1] == ' '
260
 
        s = s[:-1]
261
 
        return s
262
 
 
263
 
    def get_help_text(self, additional_see_also=None):
264
 
        """Return a text string with help for this command.
265
 
        
266
 
        :param additional_see_also: Additional help topics to be
267
 
            cross-referenced.
268
 
        """
269
 
        doc = self.help()
270
 
        if doc is None:
271
 
            raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
272
 
 
273
 
        result = ""
274
 
        result += 'usage: %s\n' % self._usage()
275
 
 
276
 
        if self.aliases:
277
 
            result += 'aliases: '
278
 
            result += ', '.join(self.aliases) + '\n'
279
 
 
280
 
        result += '\n'
281
 
 
282
 
        plugin_name = self.plugin_name()
283
 
        if plugin_name is not None:
284
 
            result += '(From plugin "%s")' % plugin_name
285
 
            result += '\n\n'
286
 
 
287
 
        result += doc
288
 
        if result[-1] != '\n':
289
 
            result += '\n'
290
 
        result += '\n'
291
 
        result += option.get_optparser(self.options()).format_option_help()
292
 
        see_also = self.get_see_also(additional_see_also)
293
 
        if see_also:
294
 
            result += '\nSee also: '
295
 
            result += ', '.join(see_also)
296
 
            result += '\n'
297
 
        return result
298
 
 
299
 
    def get_help_topic(self):
300
 
        """Return the commands help topic - its name."""
301
 
        return self.name()
302
 
 
303
 
    def get_see_also(self, additional_terms=None):
304
 
        """Return a list of help topics that are related to this ommand.
305
 
        
306
 
        The list is derived from the content of the _see_also attribute. Any
307
 
        duplicates are removed and the result is in lexical order.
308
 
        :param additional_terms: Additional help topics to cross-reference.
309
 
        :return: A list of help topics.
310
 
        """
311
 
        see_also = set(getattr(self, '_see_also', []))
312
 
        if additional_terms:
313
 
            see_also.update(additional_terms)
314
 
        return sorted(see_also)
315
 
 
316
 
    def options(self):
317
 
        """Return dict of valid options for this command.
318
 
 
319
 
        Maps from long option name to option object."""
320
 
        r = dict()
321
 
        r['help'] = option.Option.OPTIONS['help']
322
 
        for o in self.takes_options:
323
 
            if isinstance(o, basestring):
324
 
                o = option.Option.OPTIONS[o]
325
 
            r[o.name] = o
326
 
        return r
327
 
 
328
 
    def _setup_outf(self):
329
 
        """Return a file linked to stdout, which has proper encoding."""
330
 
        assert self.encoding_type in ['strict', 'exact', 'replace']
331
 
 
332
 
        # Originally I was using self.stdout, but that looks
333
 
        # *way* too much like sys.stdout
334
 
        if self.encoding_type == 'exact':
335
 
            # force sys.stdout to be binary stream on win32
336
 
            if sys.platform == 'win32':
337
 
                fileno = getattr(sys.stdout, 'fileno', None)
338
 
                if fileno:
339
 
                    import msvcrt
340
 
                    msvcrt.setmode(fileno(), os.O_BINARY)
341
 
            self.outf = sys.stdout
342
 
            return
343
 
 
344
 
        output_encoding = osutils.get_terminal_encoding()
345
 
 
346
 
        # use 'replace' so that we don't abort if trying to write out
347
 
        # in e.g. the default C locale.
348
 
        self.outf = codecs.getwriter(output_encoding)(sys.stdout, errors=self.encoding_type)
349
 
        # For whatever reason codecs.getwriter() does not advertise its encoding
350
 
        # it just returns the encoding of the wrapped file, which is completely
351
 
        # bogus. So set the attribute, so we can find the correct encoding later.
352
 
        self.outf.encoding = output_encoding
353
 
 
354
 
    def run_argv_aliases(self, argv, alias_argv=None):
355
 
        """Parse the command line and run with extra aliases in alias_argv."""
356
 
        if argv is None:
357
 
            warn("Passing None for [] is deprecated from bzrlib 0.10",
358
 
                 DeprecationWarning, stacklevel=2)
359
 
            argv = []
360
 
        args, opts = parse_args(self, argv, alias_argv)
 
261
 
 
262
    def run_argv(self, argv):
 
263
        """Parse command line and run."""
 
264
        args, opts = parse_args(argv)
 
265
 
361
266
        if 'help' in opts:  # e.g. bzr add --help
362
 
            sys.stdout.write(self.get_help_text())
 
267
            from bzrlib.help import help_on_command
 
268
            help_on_command(self.name())
363
269
            return 0
 
270
 
 
271
        # check options are reasonable
 
272
        allowed = self.takes_options
 
273
        for oname in opts:
 
274
            if oname not in allowed:
 
275
                raise BzrCommandError("option '--%s' is not allowed for command %r"
 
276
                                      % (oname, self.name()))
 
277
 
364
278
        # mix arguments and options into one dictionary
365
279
        cmdargs = _match_argform(self.name(), self.takes_args, args)
366
280
        cmdopts = {}
370
284
        all_cmd_args = cmdargs.copy()
371
285
        all_cmd_args.update(cmdopts)
372
286
 
373
 
        self._setup_outf()
374
 
 
375
287
        return self.run(**all_cmd_args)
 
288
 
376
289
    
377
290
    def run(self):
378
291
        """Actually run the command.
384
297
        shell error code if not.  It's OK for this method to allow
385
298
        an exception to raise up.
386
299
        """
387
 
        raise NotImplementedError('no implementation of command %r'
388
 
                                  % self.name())
 
300
        raise NotImplementedError()
 
301
 
389
302
 
390
303
    def help(self):
391
304
        """Return help message for this class."""
392
 
        from inspect import getdoc
393
305
        if self.__doc__ is Command.__doc__:
394
306
            return None
395
307
        return getdoc(self)
397
309
    def name(self):
398
310
        return _unsquish_command_name(self.__class__.__name__)
399
311
 
400
 
    def plugin_name(self):
401
 
        """Get the name of the plugin that provides this command.
402
 
 
403
 
        :return: The name of the plugin or None if the command is builtin.
404
 
        """
405
 
        mod_parts = self.__module__.split('.')
406
 
        if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
407
 
            return mod_parts[2]
408
 
        else:
409
 
            return None
410
 
 
411
 
 
412
 
# Technically, this function hasn't been use in a *really* long time
413
 
# but we are only deprecating it now.
414
 
@deprecated_function(zero_eleven)
 
312
 
415
313
def parse_spec(spec):
416
314
    """
417
315
    >>> parse_spec(None)
443
341
        parsed = [spec, None]
444
342
    return parsed
445
343
 
446
 
def parse_args(command, argv, alias_argv=None):
 
344
 
 
345
 
 
346
 
 
347
# list of all available options; the rhs can be either None for an
 
348
# option that takes no argument, or a constructor function that checks
 
349
# the type.
 
350
OPTIONS = {
 
351
    'all':                    None,
 
352
    'diff-options':           str,
 
353
    'help':                   None,
 
354
    'file':                   unicode,
 
355
    'force':                  None,
 
356
    'format':                 unicode,
 
357
    'forward':                None,
 
358
    'message':                unicode,
 
359
    'no-recurse':             None,
 
360
    'profile':                None,
 
361
    'revision':               _parse_revision_str,
 
362
    'short':                  None,
 
363
    'show-ids':               None,
 
364
    'timezone':               str,
 
365
    'verbose':                None,
 
366
    'version':                None,
 
367
    'email':                  None,
 
368
    'unchanged':              None,
 
369
    'update':                 None,
 
370
    'long':                   None,
 
371
    'root':                   str,
 
372
    'no-backup':              None,
 
373
    'merge-type':             get_merge_type,
 
374
    'pattern':                str,
 
375
    }
 
376
 
 
377
SHORT_OPTIONS = {
 
378
    'F':                      'file', 
 
379
    'h':                      'help',
 
380
    'm':                      'message',
 
381
    'r':                      'revision',
 
382
    'v':                      'verbose',
 
383
    'l':                      'long',
 
384
}
 
385
 
 
386
 
 
387
def parse_args(argv):
447
388
    """Parse command line.
448
389
    
449
390
    Arguments and options are parsed at this level before being passed
450
391
    down to specific command handlers.  This routine knows, from a
451
392
    lookup table, something about the available options, what optargs
452
393
    they take, and which commands will accept them.
 
394
 
 
395
    >>> parse_args('--help'.split())
 
396
    ([], {'help': True})
 
397
    >>> parse_args('help -- --invalidcmd'.split())
 
398
    (['help', '--invalidcmd'], {})
 
399
    >>> parse_args('--version'.split())
 
400
    ([], {'version': True})
 
401
    >>> parse_args('status --all'.split())
 
402
    (['status'], {'all': True})
 
403
    >>> parse_args('commit --message=biter'.split())
 
404
    (['commit'], {'message': u'biter'})
 
405
    >>> parse_args('log -r 500'.split())
 
406
    (['log'], {'revision': [500]})
 
407
    >>> parse_args('log -r500..600'.split())
 
408
    (['log'], {'revision': [500, 600]})
 
409
    >>> parse_args('log -vr500..600'.split())
 
410
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
411
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
412
    (['log'], {'revision': ['v500', 600]})
453
413
    """
454
 
    # TODO: make it a method of the Command?
455
 
    parser = option.get_optparser(command.options())
456
 
    if alias_argv is not None:
457
 
        args = alias_argv + argv
458
 
    else:
459
 
        args = argv
460
 
 
461
 
    options, args = parser.parse_args(args)
462
 
    opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
463
 
                 v is not option.OptionParser.DEFAULT_VALUE])
 
414
    args = []
 
415
    opts = {}
 
416
 
 
417
    argsover = False
 
418
    while argv:
 
419
        a = argv.pop(0)
 
420
        if not argsover and a[0] == '-':
 
421
            # option names must not be unicode
 
422
            a = str(a)
 
423
            optarg = None
 
424
            if a[1] == '-':
 
425
                if a == '--':
 
426
                    # We've received a standalone -- No more flags
 
427
                    argsover = True
 
428
                    continue
 
429
                mutter("  got option %r" % a)
 
430
                if '=' in a:
 
431
                    optname, optarg = a[2:].split('=', 1)
 
432
                else:
 
433
                    optname = a[2:]
 
434
                if optname not in OPTIONS:
 
435
                    raise BzrError('unknown long option %r' % a)
 
436
            else:
 
437
                shortopt = a[1:]
 
438
                if shortopt in SHORT_OPTIONS:
 
439
                    # Multi-character options must have a space to delimit
 
440
                    # their value
 
441
                    optname = SHORT_OPTIONS[shortopt]
 
442
                else:
 
443
                    # Single character short options, can be chained,
 
444
                    # and have their value appended to their name
 
445
                    shortopt = a[1:2]
 
446
                    if shortopt not in SHORT_OPTIONS:
 
447
                        # We didn't find the multi-character name, and we
 
448
                        # didn't find the single char name
 
449
                        raise BzrError('unknown short option %r' % a)
 
450
                    optname = SHORT_OPTIONS[shortopt]
 
451
 
 
452
                    if a[2:]:
 
453
                        # There are extra things on this option
 
454
                        # see if it is the value, or if it is another
 
455
                        # short option
 
456
                        optargfn = OPTIONS[optname]
 
457
                        if optargfn is None:
 
458
                            # This option does not take an argument, so the
 
459
                            # next entry is another short option, pack it back
 
460
                            # into the list
 
461
                            argv.insert(0, '-' + a[2:])
 
462
                        else:
 
463
                            # This option takes an argument, so pack it
 
464
                            # into the array
 
465
                            optarg = a[2:]
 
466
            
 
467
            if optname in opts:
 
468
                # XXX: Do we ever want to support this, e.g. for -r?
 
469
                raise BzrError('repeated option %r' % a)
 
470
                
 
471
            optargfn = OPTIONS[optname]
 
472
            if optargfn:
 
473
                if optarg == None:
 
474
                    if not argv:
 
475
                        raise BzrError('option %r needs an argument' % a)
 
476
                    else:
 
477
                        optarg = argv.pop(0)
 
478
                opts[optname] = optargfn(optarg)
 
479
            else:
 
480
                if optarg != None:
 
481
                    raise BzrError('option %r takes no argument' % optname)
 
482
                opts[optname] = True
 
483
        else:
 
484
            args.append(a)
 
485
 
464
486
    return args, opts
465
487
 
466
488
 
 
489
 
 
490
 
467
491
def _match_argform(cmd, takes_args, args):
468
492
    argdict = {}
469
493
 
481
505
                argdict[argname + '_list'] = None
482
506
        elif ap[-1] == '+':
483
507
            if not args:
484
 
                raise errors.BzrCommandError("command %r needs one or more %s"
485
 
                                             % (cmd, argname.upper()))
 
508
                raise BzrCommandError("command %r needs one or more %s"
 
509
                        % (cmd, argname.upper()))
486
510
            else:
487
511
                argdict[argname + '_list'] = args[:]
488
512
                args = []
489
513
        elif ap[-1] == '$': # all but one
490
514
            if len(args) < 2:
491
 
                raise errors.BzrCommandError("command %r needs one or more %s"
492
 
                                             % (cmd, argname.upper()))
 
515
                raise BzrCommandError("command %r needs one or more %s"
 
516
                        % (cmd, argname.upper()))
493
517
            argdict[argname + '_list'] = args[:-1]
494
 
            args[:-1] = []
 
518
            args[:-1] = []                
495
519
        else:
496
520
            # just a plain arg
497
521
            argname = ap
498
522
            if not args:
499
 
                raise errors.BzrCommandError("command %r requires argument %s"
500
 
                               % (cmd, argname.upper()))
 
523
                raise BzrCommandError("command %r requires argument %s"
 
524
                        % (cmd, argname.upper()))
501
525
            else:
502
526
                argdict[argname] = args.pop(0)
503
527
            
504
528
    if args:
505
 
        raise errors.BzrCommandError("extra argument to command %s: %s"
506
 
                                     % (cmd, args[0]))
 
529
        raise BzrCommandError("extra argument to command %s: %s"
 
530
                              % (cmd, args[0]))
507
531
 
508
532
    return argdict
509
533
 
512
536
def apply_profiled(the_callable, *args, **kwargs):
513
537
    import hotshot
514
538
    import tempfile
515
 
    import hotshot.stats
516
539
    pffileno, pfname = tempfile.mkstemp()
517
540
    try:
518
541
        prof = hotshot.Profile(pfname)
520
543
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
521
544
        finally:
522
545
            prof.close()
 
546
 
 
547
        import hotshot.stats
523
548
        stats = hotshot.stats.load(pfname)
524
 
        stats.strip_dirs()
525
 
        stats.sort_stats('cum')   # 'time'
 
549
        #stats.strip_dirs()
 
550
        stats.sort_stats('time')
526
551
        ## XXX: Might like to write to stderr or the trace file instead but
527
552
        ## print_stats seems hardcoded to stdout
528
553
        stats.print_stats(20)
 
554
 
529
555
        return ret
530
556
    finally:
531
557
        os.close(pffileno)
532
558
        os.remove(pfname)
533
559
 
534
560
 
535
 
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
536
 
    from bzrlib.lsprof import profile
537
 
    import cPickle
538
 
    ret, stats = profile(the_callable, *args, **kwargs)
539
 
    stats.sort()
540
 
    if filename is None:
541
 
        stats.pprint()
542
 
    else:
543
 
        stats.save(filename)
544
 
        print 'Profile data written to %r.' % filename
545
 
    return ret
546
 
 
547
 
 
548
 
def get_alias(cmd, config=None):
549
 
    """Return an expanded alias, or None if no alias exists.
550
 
 
551
 
    cmd
552
 
        Command to be checked for an alias.
553
 
    config
554
 
        Used to specify an alternative config to use,
555
 
        which is especially useful for testing.
556
 
        If it is unspecified, the global config will be used.
557
 
    """
558
 
    if config is None:
559
 
        import bzrlib.config
560
 
        config = bzrlib.config.GlobalConfig()
561
 
    alias = config.get_alias(cmd)
562
 
    if (alias):
563
 
        import shlex
564
 
        return [a.decode('utf-8') for a in shlex.split(alias.encode('utf-8'))]
565
 
    return None
566
 
 
567
 
 
568
561
def run_bzr(argv):
569
562
    """Execute a command.
570
563
 
573
566
    
574
567
    argv
575
568
       The command-line arguments, without the program name from argv[0]
576
 
       These should already be decoded. All library/test code calling
577
 
       run_bzr should be passing valid strings (don't need decoding).
578
569
    
579
570
    Returns a command status or raises an exception.
580
571
 
584
575
    --no-plugins
585
576
        Do not load plugin modules at all
586
577
 
587
 
    --no-aliases
588
 
        Do not allow aliases
589
 
 
590
578
    --builtin
591
579
        Only use builtin commands.  (Plugins are still allowed to change
592
580
        other behaviour.)
593
581
 
594
582
    --profile
595
 
        Run under the Python hotshot profiler.
596
 
 
597
 
    --lsprof
598
 
        Run under the Python lsprof profiler.
 
583
        Run under the Python profiler.
599
584
    """
600
 
    argv = list(argv)
601
 
    trace.mutter("bzr arguments: %r", argv)
 
585
    
 
586
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
602
587
 
603
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin =  \
604
 
                opt_no_aliases = False
605
 
    opt_lsprof_file = None
 
588
    opt_profile = opt_no_plugins = opt_builtin = False
606
589
 
607
590
    # --no-plugins is handled specially at a very early stage. We need
608
591
    # to load plugins before doing other command parsing so that they
609
592
    # can override commands, but this needs to happen first.
610
593
 
611
 
    argv_copy = []
612
 
    i = 0
613
 
    while i < len(argv):
614
 
        a = argv[i]
 
594
    for a in argv:
615
595
        if a == '--profile':
616
596
            opt_profile = True
617
 
        elif a == '--lsprof':
618
 
            opt_lsprof = True
619
 
        elif a == '--lsprof-file':
620
 
            opt_lsprof = True
621
 
            opt_lsprof_file = argv[i + 1]
622
 
            i += 1
623
597
        elif a == '--no-plugins':
624
598
            opt_no_plugins = True
625
 
        elif a == '--no-aliases':
626
 
            opt_no_aliases = True
627
599
        elif a == '--builtin':
628
600
            opt_builtin = True
629
 
        elif a in ('--quiet', '-q'):
630
 
            trace.be_quiet()
631
 
        elif a.startswith('-D'):
632
 
            debug.debug_flags.add(a[2:])
633
601
        else:
634
 
            argv_copy.append(a)
635
 
        i += 1
 
602
            break
 
603
        argv.remove(a)
636
604
 
637
 
    argv = argv_copy
638
 
    if (not argv):
639
 
        from bzrlib.builtins import cmd_help
640
 
        cmd_help().run_argv_aliases([])
 
605
    if (not argv) or (argv[0] == '--help'):
 
606
        from bzrlib.help import help
 
607
        if len(argv) > 1:
 
608
            help(argv[1])
 
609
        else:
 
610
            help()
641
611
        return 0
642
612
 
643
613
    if argv[0] == '--version':
644
 
        from bzrlib.version import show_version
 
614
        from bzrlib.builtins import show_version
645
615
        show_version()
646
616
        return 0
647
617
        
648
618
    if not opt_no_plugins:
649
619
        from bzrlib.plugin import load_plugins
650
620
        load_plugins()
651
 
    else:
652
 
        from bzrlib.plugin import disable_plugins
653
 
        disable_plugins()
654
 
 
655
 
    alias_argv = None
656
 
 
657
 
    if not opt_no_aliases:
658
 
        alias_argv = get_alias(argv[0])
659
 
        if alias_argv:
660
 
            alias_argv = [a.decode(bzrlib.user_encoding) for a in alias_argv]
661
 
            argv[0] = alias_argv.pop(0)
662
 
 
663
 
    cmd = argv.pop(0)
664
 
    # We want only 'ascii' command names, but the user may have typed
665
 
    # in a Unicode name. In that case, they should just get a
666
 
    # 'command not found' error later.
 
621
 
 
622
    cmd = str(argv.pop(0))
667
623
 
668
624
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
669
 
    run = cmd_obj.run_argv_aliases
670
 
    run_argv = [argv, alias_argv]
671
 
 
672
 
    try:
673
 
        if opt_lsprof:
674
 
            ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
675
 
        elif opt_profile:
676
 
            ret = apply_profiled(run, *run_argv)
677
 
        else:
678
 
            ret = run(*run_argv)
679
 
        return ret or 0
680
 
    finally:
681
 
        # reset, in case we may do other commands later within the same process
682
 
        trace.be_quiet(False)
683
 
 
684
 
def display_command(func):
685
 
    """Decorator that suppresses pipe/interrupt errors."""
686
 
    def ignore_pipe(*args, **kwargs):
687
 
        try:
688
 
            result = func(*args, **kwargs)
689
 
            sys.stdout.flush()
690
 
            return result
691
 
        except IOError, e:
692
 
            if getattr(e, 'errno', None) is None:
693
 
                raise
694
 
            if e.errno != errno.EPIPE:
695
 
                # Win32 raises IOError with errno=0 on a broken pipe
696
 
                if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
697
 
                    raise
698
 
            pass
699
 
        except KeyboardInterrupt:
700
 
            pass
701
 
    return ignore_pipe
 
625
 
 
626
    if opt_profile:
 
627
        ret = apply_profiled(cmd_obj.run_argv, argv)
 
628
    else:
 
629
        ret = cmd_obj.run_argv(argv)
 
630
    return ret or 0
702
631
 
703
632
 
704
633
def main(argv):
705
634
    import bzrlib.ui
706
 
    from bzrlib.ui.text import TextUIFactory
707
 
    bzrlib.ui.ui_factory = TextUIFactory()
708
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
709
 
    ret = run_bzr_catch_errors(argv)
710
 
    trace.mutter("return code %d", ret)
711
 
    return ret
712
 
 
713
 
 
714
 
def run_bzr_catch_errors(argv):
 
635
    bzrlib.trace.log_startup(argv)
 
636
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
637
 
715
638
    try:
716
639
        try:
717
 
            return run_bzr(argv)
 
640
            return run_bzr(argv[1:])
718
641
        finally:
719
642
            # do this here inside the exception wrappers to catch EPIPE
720
643
            sys.stdout.flush()
721
 
    except (KeyboardInterrupt, Exception), e:
722
 
        # used to handle AssertionError and KeyboardInterrupt
723
 
        # specially here, but hopefully they're handled ok by the logger now
724
 
        trace.report_exception(sys.exc_info(), sys.stderr)
725
 
        if os.environ.get('BZR_PDB'):
726
 
            print '**** entering debugger'
727
 
            import pdb
728
 
            pdb.post_mortem(sys.exc_traceback)
 
644
    except BzrCommandError, e:
 
645
        # command line syntax error, etc
 
646
        log_error(str(e))
 
647
        return 1
 
648
    except BzrError, e:
 
649
        bzrlib.trace.log_exception()
 
650
        return 1
 
651
    except AssertionError, e:
 
652
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
729
653
        return 3
730
 
 
731
 
 
732
 
class HelpCommandIndex(object):
733
 
    """A index for bzr help that returns commands."""
734
 
 
735
 
    def __init__(self):
736
 
        self.prefix = 'commands/'
737
 
 
738
 
    def get_topics(self, topic):
739
 
        """Search for topic amongst commands.
740
 
 
741
 
        :param topic: A topic to search for.
742
 
        :return: A list which is either empty or contains a single
743
 
            Command entry.
744
 
        """
745
 
        if topic and topic.startswith(self.prefix):
746
 
            topic = topic[len(self.prefix):]
747
 
        try:
748
 
            cmd = _get_cmd_object(topic)
749
 
        except KeyError:
750
 
            return []
 
654
    except KeyboardInterrupt, e:
 
655
        bzrlib.trace.note('interrupted')
 
656
        return 2
 
657
    except Exception, e:
 
658
        import errno
 
659
        if (isinstance(e, IOError) 
 
660
            and hasattr(e, 'errno')
 
661
            and e.errno == errno.EPIPE):
 
662
            bzrlib.trace.note('broken pipe')
 
663
            return 2
751
664
        else:
752
 
            return [cmd]
 
665
            bzrlib.trace.log_exception()
 
666
            return 2
753
667
 
754
668
 
755
669
if __name__ == '__main__':