~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Aaron Bentley
  • Date: 2005-09-13 02:42:07 UTC
  • mto: (1185.1.16)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: aaron.bentley@utoronto.ca-20050913024207-489d573af4b76c4d
Fixed issues with pull not having a default location after branch

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
 
 
153
def get_merge_type(typestring):
 
154
    """Attempt to find the merge class/factory associated with a string."""
 
155
    from merge import merge_types
 
156
    try:
 
157
        return merge_types[typestring][0]
 
158
    except KeyError:
 
159
        templ = '%s%%7s: %%s' % (' '*12)
 
160
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
161
        type_list = '\n'.join(lines)
 
162
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
163
            (typestring, type_list)
 
164
        raise BzrCommandError(msg)
 
165
 
 
166
 
100
167
def _builtin_commands():
101
168
    import bzrlib.builtins
102
169
    r = {}
103
170
    builtins = bzrlib.builtins.__dict__
104
171
    for name in builtins:
105
172
        if name.startswith("cmd_"):
106
 
            real_name = _unsquish_command_name(name)
 
173
            real_name = _unsquish_command_name(name)        
107
174
            r[real_name] = builtins[name]
108
175
    return r
 
176
 
109
177
            
110
178
 
111
179
def builtin_command_names():
137
205
    plugins_override
138
206
        If true, plugin commands can override builtins.
139
207
    """
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
208
    from bzrlib.externalcommand import ExternalCommand
149
209
 
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.
 
210
    cmd_name = str(cmd_name)            # not unicode
154
211
 
155
212
    # first look up this command under the specified name
156
213
    cmds = _get_cmd_dict(plugins_override=plugins_override)
167
224
    cmd_obj = ExternalCommand.find_command(cmd_name)
168
225
    if cmd_obj:
169
226
        return cmd_obj
170
 
    raise KeyError
 
227
 
 
228
    raise BzrCommandError("unknown command %r" % cmd_name)
171
229
 
172
230
 
173
231
class Command(object):
195
253
        List of argument forms, marked with whether they are optional,
196
254
        repeated, etc.
197
255
 
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
256
    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().
 
257
        List of options that may be given for this command.
210
258
 
211
259
    hidden
212
260
        If true, this command isn't advertised.  This is typically
213
261
        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
262
    """
231
263
    aliases = []
 
264
    
232
265
    takes_args = []
233
266
    takes_options = []
234
 
    encoding_type = 'strict'
235
267
 
236
268
    hidden = False
237
269
    
240
272
        if self.__doc__ == Command.__doc__:
241
273
            warn("No help message set for %r" % self)
242
274
 
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)
 
275
 
 
276
    def run_argv(self, argv):
 
277
        """Parse command line and run."""
 
278
        args, opts = parse_args(argv)
 
279
 
361
280
        if 'help' in opts:  # e.g. bzr add --help
362
 
            sys.stdout.write(self.get_help_text())
 
281
            from bzrlib.help import help_on_command
 
282
            help_on_command(self.name())
363
283
            return 0
 
284
 
 
285
        # check options are reasonable
 
286
        allowed = self.takes_options
 
287
        for oname in opts:
 
288
            if oname not in allowed:
 
289
                raise BzrCommandError("option '--%s' is not allowed for command %r"
 
290
                                      % (oname, self.name()))
 
291
 
364
292
        # mix arguments and options into one dictionary
365
293
        cmdargs = _match_argform(self.name(), self.takes_args, args)
366
294
        cmdopts = {}
370
298
        all_cmd_args = cmdargs.copy()
371
299
        all_cmd_args.update(cmdopts)
372
300
 
373
 
        self._setup_outf()
374
 
 
375
301
        return self.run(**all_cmd_args)
 
302
 
376
303
    
377
304
    def run(self):
378
305
        """Actually run the command.
384
311
        shell error code if not.  It's OK for this method to allow
385
312
        an exception to raise up.
386
313
        """
387
 
        raise NotImplementedError('no implementation of command %r'
388
 
                                  % self.name())
 
314
        raise NotImplementedError()
 
315
 
389
316
 
390
317
    def help(self):
391
318
        """Return help message for this class."""
392
 
        from inspect import getdoc
393
319
        if self.__doc__ is Command.__doc__:
394
320
            return None
395
321
        return getdoc(self)
397
323
    def name(self):
398
324
        return _unsquish_command_name(self.__class__.__name__)
399
325
 
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)
 
326
 
415
327
def parse_spec(spec):
416
328
    """
417
329
    >>> parse_spec(None)
443
355
        parsed = [spec, None]
444
356
    return parsed
445
357
 
446
 
def parse_args(command, argv, alias_argv=None):
 
358
 
 
359
 
 
360
 
 
361
# list of all available options; the rhs can be either None for an
 
362
# option that takes no argument, or a constructor function that checks
 
363
# the type.
 
364
OPTIONS = {
 
365
    'all':                    None,
 
366
    'diff-options':           str,
 
367
    'help':                   None,
 
368
    'file':                   unicode,
 
369
    'force':                  None,
 
370
    'format':                 unicode,
 
371
    'forward':                None,
 
372
    'message':                unicode,
 
373
    'no-recurse':             None,
 
374
    'profile':                None,
 
375
    'revision':               _parse_revision_str,
 
376
    'short':                  None,
 
377
    'show-ids':               None,
 
378
    'timezone':               str,
 
379
    'verbose':                None,
 
380
    'version':                None,
 
381
    'email':                  None,
 
382
    'unchanged':              None,
 
383
    'update':                 None,
 
384
    'long':                   None,
 
385
    'root':                   str,
 
386
    'no-backup':              None,
 
387
    'merge-type':             get_merge_type,
 
388
    'pattern':                str,
 
389
    }
 
390
 
 
391
SHORT_OPTIONS = {
 
392
    'F':                      'file', 
 
393
    'h':                      'help',
 
394
    'm':                      'message',
 
395
    'r':                      'revision',
 
396
    'v':                      'verbose',
 
397
    'l':                      'long',
 
398
}
 
399
 
 
400
 
 
401
def parse_args(argv):
447
402
    """Parse command line.
448
403
    
449
404
    Arguments and options are parsed at this level before being passed
450
405
    down to specific command handlers.  This routine knows, from a
451
406
    lookup table, something about the available options, what optargs
452
407
    they take, and which commands will accept them.
 
408
 
 
409
    >>> parse_args('--help'.split())
 
410
    ([], {'help': True})
 
411
    >>> parse_args('help -- --invalidcmd'.split())
 
412
    (['help', '--invalidcmd'], {})
 
413
    >>> parse_args('--version'.split())
 
414
    ([], {'version': True})
 
415
    >>> parse_args('status --all'.split())
 
416
    (['status'], {'all': True})
 
417
    >>> parse_args('commit --message=biter'.split())
 
418
    (['commit'], {'message': u'biter'})
 
419
    >>> parse_args('log -r 500'.split())
 
420
    (['log'], {'revision': [500]})
 
421
    >>> parse_args('log -r500..600'.split())
 
422
    (['log'], {'revision': [500, 600]})
 
423
    >>> parse_args('log -vr500..600'.split())
 
424
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
425
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
426
    (['log'], {'revision': ['v500', 600]})
453
427
    """
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])
 
428
    args = []
 
429
    opts = {}
 
430
 
 
431
    argsover = False
 
432
    while argv:
 
433
        a = argv.pop(0)
 
434
        if not argsover and a[0] == '-':
 
435
            # option names must not be unicode
 
436
            a = str(a)
 
437
            optarg = None
 
438
            if a[1] == '-':
 
439
                if a == '--':
 
440
                    # We've received a standalone -- No more flags
 
441
                    argsover = True
 
442
                    continue
 
443
                mutter("  got option %r" % a)
 
444
                if '=' in a:
 
445
                    optname, optarg = a[2:].split('=', 1)
 
446
                else:
 
447
                    optname = a[2:]
 
448
                if optname not in OPTIONS:
 
449
                    raise BzrError('unknown long option %r' % a)
 
450
            else:
 
451
                shortopt = a[1:]
 
452
                if shortopt in SHORT_OPTIONS:
 
453
                    # Multi-character options must have a space to delimit
 
454
                    # their value
 
455
                    optname = SHORT_OPTIONS[shortopt]
 
456
                else:
 
457
                    # Single character short options, can be chained,
 
458
                    # and have their value appended to their name
 
459
                    shortopt = a[1:2]
 
460
                    if shortopt not in SHORT_OPTIONS:
 
461
                        # We didn't find the multi-character name, and we
 
462
                        # didn't find the single char name
 
463
                        raise BzrError('unknown short option %r' % a)
 
464
                    optname = SHORT_OPTIONS[shortopt]
 
465
 
 
466
                    if a[2:]:
 
467
                        # There are extra things on this option
 
468
                        # see if it is the value, or if it is another
 
469
                        # short option
 
470
                        optargfn = OPTIONS[optname]
 
471
                        if optargfn is None:
 
472
                            # This option does not take an argument, so the
 
473
                            # next entry is another short option, pack it back
 
474
                            # into the list
 
475
                            argv.insert(0, '-' + a[2:])
 
476
                        else:
 
477
                            # This option takes an argument, so pack it
 
478
                            # into the array
 
479
                            optarg = a[2:]
 
480
            
 
481
            if optname in opts:
 
482
                # XXX: Do we ever want to support this, e.g. for -r?
 
483
                raise BzrError('repeated option %r' % a)
 
484
                
 
485
            optargfn = OPTIONS[optname]
 
486
            if optargfn:
 
487
                if optarg == None:
 
488
                    if not argv:
 
489
                        raise BzrError('option %r needs an argument' % a)
 
490
                    else:
 
491
                        optarg = argv.pop(0)
 
492
                opts[optname] = optargfn(optarg)
 
493
            else:
 
494
                if optarg != None:
 
495
                    raise BzrError('option %r takes no argument' % optname)
 
496
                opts[optname] = True
 
497
        else:
 
498
            args.append(a)
 
499
 
464
500
    return args, opts
465
501
 
466
502
 
 
503
 
 
504
 
467
505
def _match_argform(cmd, takes_args, args):
468
506
    argdict = {}
469
507
 
481
519
                argdict[argname + '_list'] = None
482
520
        elif ap[-1] == '+':
483
521
            if not args:
484
 
                raise errors.BzrCommandError("command %r needs one or more %s"
485
 
                                             % (cmd, argname.upper()))
 
522
                raise BzrCommandError("command %r needs one or more %s"
 
523
                        % (cmd, argname.upper()))
486
524
            else:
487
525
                argdict[argname + '_list'] = args[:]
488
526
                args = []
489
527
        elif ap[-1] == '$': # all but one
490
528
            if len(args) < 2:
491
 
                raise errors.BzrCommandError("command %r needs one or more %s"
492
 
                                             % (cmd, argname.upper()))
 
529
                raise BzrCommandError("command %r needs one or more %s"
 
530
                        % (cmd, argname.upper()))
493
531
            argdict[argname + '_list'] = args[:-1]
494
 
            args[:-1] = []
 
532
            args[:-1] = []                
495
533
        else:
496
534
            # just a plain arg
497
535
            argname = ap
498
536
            if not args:
499
 
                raise errors.BzrCommandError("command %r requires argument %s"
500
 
                               % (cmd, argname.upper()))
 
537
                raise BzrCommandError("command %r requires argument %s"
 
538
                        % (cmd, argname.upper()))
501
539
            else:
502
540
                argdict[argname] = args.pop(0)
503
541
            
504
542
    if args:
505
 
        raise errors.BzrCommandError("extra argument to command %s: %s"
506
 
                                     % (cmd, args[0]))
 
543
        raise BzrCommandError("extra argument to command %s: %s"
 
544
                              % (cmd, args[0]))
507
545
 
508
546
    return argdict
509
547
 
512
550
def apply_profiled(the_callable, *args, **kwargs):
513
551
    import hotshot
514
552
    import tempfile
515
 
    import hotshot.stats
516
553
    pffileno, pfname = tempfile.mkstemp()
517
554
    try:
518
555
        prof = hotshot.Profile(pfname)
520
557
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
521
558
        finally:
522
559
            prof.close()
 
560
 
 
561
        import hotshot.stats
523
562
        stats = hotshot.stats.load(pfname)
524
 
        stats.strip_dirs()
525
 
        stats.sort_stats('cum')   # 'time'
 
563
        #stats.strip_dirs()
 
564
        stats.sort_stats('time')
526
565
        ## XXX: Might like to write to stderr or the trace file instead but
527
566
        ## print_stats seems hardcoded to stdout
528
567
        stats.print_stats(20)
 
568
 
529
569
        return ret
530
570
    finally:
531
571
        os.close(pffileno)
532
572
        os.remove(pfname)
533
573
 
534
574
 
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.freeze()
544
 
        cPickle.dump(stats, open(filename, 'w'), 2)
545
 
        print 'Profile data written to %r.' % filename
546
 
    return ret
547
 
 
548
 
 
549
 
def get_alias(cmd, config=None):
550
 
    """Return an expanded alias, or None if no alias exists.
551
 
 
552
 
    cmd
553
 
        Command to be checked for an alias.
554
 
    config
555
 
        Used to specify an alternative config to use,
556
 
        which is especially useful for testing.
557
 
        If it is unspecified, the global config will be used.
558
 
    """
559
 
    if config is None:
560
 
        import bzrlib.config
561
 
        config = bzrlib.config.GlobalConfig()
562
 
    alias = config.get_alias(cmd)
563
 
    if (alias):
564
 
        import shlex
565
 
        return [a.decode('utf-8') for a in shlex.split(alias.encode('utf-8'))]
566
 
    return None
567
 
 
568
 
 
569
575
def run_bzr(argv):
570
576
    """Execute a command.
571
577
 
574
580
    
575
581
    argv
576
582
       The command-line arguments, without the program name from argv[0]
577
 
       These should already be decoded. All library/test code calling
578
 
       run_bzr should be passing valid strings (don't need decoding).
579
583
    
580
584
    Returns a command status or raises an exception.
581
585
 
585
589
    --no-plugins
586
590
        Do not load plugin modules at all
587
591
 
588
 
    --no-aliases
589
 
        Do not allow aliases
590
 
 
591
592
    --builtin
592
593
        Only use builtin commands.  (Plugins are still allowed to change
593
594
        other behaviour.)
594
595
 
595
596
    --profile
596
 
        Run under the Python hotshot profiler.
597
 
 
598
 
    --lsprof
599
 
        Run under the Python lsprof profiler.
 
597
        Run under the Python profiler.
600
598
    """
601
 
    argv = list(argv)
602
 
    trace.mutter("bzr arguments: %r", argv)
 
599
    
 
600
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
603
601
 
604
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin =  \
605
 
                opt_no_aliases = False
606
 
    opt_lsprof_file = None
 
602
    opt_profile = opt_no_plugins = opt_builtin = False
607
603
 
608
604
    # --no-plugins is handled specially at a very early stage. We need
609
605
    # to load plugins before doing other command parsing so that they
610
606
    # can override commands, but this needs to happen first.
611
607
 
612
 
    argv_copy = []
613
 
    i = 0
614
 
    while i < len(argv):
615
 
        a = argv[i]
 
608
    for a in argv:
616
609
        if a == '--profile':
617
610
            opt_profile = True
618
 
        elif a == '--lsprof':
619
 
            opt_lsprof = True
620
 
        elif a == '--lsprof-file':
621
 
            opt_lsprof = True
622
 
            opt_lsprof_file = argv[i + 1]
623
 
            i += 1
624
611
        elif a == '--no-plugins':
625
612
            opt_no_plugins = True
626
 
        elif a == '--no-aliases':
627
 
            opt_no_aliases = True
628
613
        elif a == '--builtin':
629
614
            opt_builtin = True
630
 
        elif a in ('--quiet', '-q'):
631
 
            trace.be_quiet()
632
 
        elif a.startswith('-D'):
633
 
            debug.debug_flags.add(a[2:])
634
615
        else:
635
 
            argv_copy.append(a)
636
 
        i += 1
 
616
            break
 
617
        argv.remove(a)
637
618
 
638
 
    argv = argv_copy
639
 
    if (not argv):
640
 
        from bzrlib.builtins import cmd_help
641
 
        cmd_help().run_argv_aliases([])
 
619
    if (not argv) or (argv[0] == '--help'):
 
620
        from bzrlib.help import help
 
621
        if len(argv) > 1:
 
622
            help(argv[1])
 
623
        else:
 
624
            help()
642
625
        return 0
643
626
 
644
627
    if argv[0] == '--version':
645
 
        from bzrlib.version import show_version
 
628
        from bzrlib.builtins import show_version
646
629
        show_version()
647
630
        return 0
648
631
        
649
632
    if not opt_no_plugins:
650
633
        from bzrlib.plugin import load_plugins
651
634
        load_plugins()
652
 
    else:
653
 
        from bzrlib.plugin import disable_plugins
654
 
        disable_plugins()
655
 
 
656
 
    alias_argv = None
657
 
 
658
 
    if not opt_no_aliases:
659
 
        alias_argv = get_alias(argv[0])
660
 
        if alias_argv:
661
 
            alias_argv = [a.decode(bzrlib.user_encoding) for a in alias_argv]
662
 
            argv[0] = alias_argv.pop(0)
663
 
 
664
 
    cmd = argv.pop(0)
665
 
    # We want only 'ascii' command names, but the user may have typed
666
 
    # in a Unicode name. In that case, they should just get a
667
 
    # 'command not found' error later.
 
635
 
 
636
    cmd = str(argv.pop(0))
668
637
 
669
638
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
670
 
    run = cmd_obj.run_argv_aliases
671
 
    run_argv = [argv, alias_argv]
672
 
 
673
 
    try:
674
 
        if opt_lsprof:
675
 
            ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
676
 
        elif opt_profile:
677
 
            ret = apply_profiled(run, *run_argv)
678
 
        else:
679
 
            ret = run(*run_argv)
680
 
        return ret or 0
681
 
    finally:
682
 
        # reset, in case we may do other commands later within the same process
683
 
        trace.be_quiet(False)
684
 
 
685
 
def display_command(func):
686
 
    """Decorator that suppresses pipe/interrupt errors."""
687
 
    def ignore_pipe(*args, **kwargs):
688
 
        try:
689
 
            result = func(*args, **kwargs)
690
 
            sys.stdout.flush()
691
 
            return result
692
 
        except IOError, e:
693
 
            if getattr(e, 'errno', None) is None:
694
 
                raise
695
 
            if e.errno != errno.EPIPE:
696
 
                # Win32 raises IOError with errno=0 on a broken pipe
697
 
                if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
698
 
                    raise
699
 
            pass
700
 
        except KeyboardInterrupt:
701
 
            pass
702
 
    return ignore_pipe
 
639
 
 
640
    if opt_profile:
 
641
        ret = apply_profiled(cmd_obj.run_argv, argv)
 
642
    else:
 
643
        ret = cmd_obj.run_argv(argv)
 
644
    return ret or 0
703
645
 
704
646
 
705
647
def main(argv):
706
648
    import bzrlib.ui
707
 
    from bzrlib.ui.text import TextUIFactory
708
 
    bzrlib.ui.ui_factory = TextUIFactory()
709
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
710
 
    ret = run_bzr_catch_errors(argv)
711
 
    trace.mutter("return code %d", ret)
712
 
    return ret
713
 
 
714
 
 
715
 
def run_bzr_catch_errors(argv):
 
649
    bzrlib.trace.log_startup(argv)
 
650
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
651
 
716
652
    try:
717
653
        try:
718
 
            return run_bzr(argv)
 
654
            return run_bzr(argv[1:])
719
655
        finally:
720
656
            # do this here inside the exception wrappers to catch EPIPE
721
657
            sys.stdout.flush()
722
 
    except (KeyboardInterrupt, Exception), e:
723
 
        # used to handle AssertionError and KeyboardInterrupt
724
 
        # specially here, but hopefully they're handled ok by the logger now
725
 
        trace.report_exception(sys.exc_info(), sys.stderr)
726
 
        if os.environ.get('BZR_PDB'):
727
 
            print '**** entering debugger'
728
 
            import pdb
729
 
            pdb.post_mortem(sys.exc_traceback)
 
658
    except BzrCommandError, e:
 
659
        # command line syntax error, etc
 
660
        log_error(str(e))
 
661
        return 1
 
662
    except BzrError, e:
 
663
        bzrlib.trace.log_exception()
 
664
        return 1
 
665
    except AssertionError, e:
 
666
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
730
667
        return 3
731
 
 
732
 
 
733
 
class HelpCommandIndex(object):
734
 
    """A index for bzr help that returns commands."""
735
 
 
736
 
    def __init__(self):
737
 
        self.prefix = 'commands/'
738
 
 
739
 
    def get_topics(self, topic):
740
 
        """Search for topic amongst commands.
741
 
 
742
 
        :param topic: A topic to search for.
743
 
        :return: A list which is either empty or contains a single
744
 
            Command entry.
745
 
        """
746
 
        if topic and topic.startswith(self.prefix):
747
 
            topic = topic[len(self.prefix):]
748
 
        try:
749
 
            cmd = _get_cmd_object(topic)
750
 
        except KeyError:
751
 
            return []
 
668
    except KeyboardInterrupt, e:
 
669
        bzrlib.trace.note('interrupted')
 
670
        return 2
 
671
    except Exception, e:
 
672
        import errno
 
673
        if (isinstance(e, IOError) 
 
674
            and hasattr(e, 'errno')
 
675
            and e.errno == errno.EPIPE):
 
676
            bzrlib.trace.note('broken pipe')
 
677
            return 2
752
678
        else:
753
 
            return [cmd]
 
679
            bzrlib.trace.log_exception()
 
680
            return 2
754
681
 
755
682
 
756
683
if __name__ == '__main__':