~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-06-30 10:00:57 UTC
  • mto: This revision was merged to the branch mainline in revision 852.
  • Revision ID: mbp@sourcefrog.net-20050630100057-32a93074b0f1faa8
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 by Canonical Ltd
2
 
 
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.
7
 
 
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.
12
 
 
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
16
 
 
17
 
 
18
 
# TODO: probably should say which arguments are candidates for glob
19
 
# expansion on windows and do that at the command level.
20
 
 
21
 
# TODO: Help messages for options.
22
 
 
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.
26
 
 
27
 
# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
28
 
# the profile output behind so it can be interactively examined?
29
 
 
30
 
import sys
31
 
import os
32
 
from warnings import warn
33
 
from inspect import getdoc
34
 
 
35
 
import bzrlib
36
 
import bzrlib.trace
37
 
from bzrlib.trace import mutter, note, log_error, warning
38
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
39
 
from bzrlib.revisionspec import RevisionSpec
40
 
from bzrlib import BZRDIR
41
 
 
42
 
plugin_cmds = {}
43
 
 
44
 
 
45
 
def register_command(cmd):
46
 
    "Utility function to help register a command"
47
 
    global plugin_cmds
48
 
    k = cmd.__name__
49
 
    if k.startswith("cmd_"):
50
 
        k_unsquished = _unsquish_command_name(k)
51
 
    else:
52
 
        k_unsquished = k
53
 
    if not plugin_cmds.has_key(k_unsquished):
54
 
        plugin_cmds[k_unsquished] = cmd
55
 
        mutter('registered plugin command %s', k_unsquished)      
56
 
    else:
57
 
        log_error('Two plugins defined the same command: %r' % k)
58
 
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
59
 
 
60
 
 
61
 
def _squish_command_name(cmd):
62
 
    return 'cmd_' + cmd.replace('-', '_')
63
 
 
64
 
 
65
 
def _unsquish_command_name(cmd):
66
 
    assert cmd.startswith("cmd_")
67
 
    return cmd[4:].replace('_','-')
68
 
 
69
 
 
70
 
def _parse_revision_str(revstr):
71
 
    """This handles a revision string -> revno.
72
 
 
73
 
    This always returns a list.  The list will have one element for
74
 
    each revision specifier supplied.
75
 
 
76
 
    >>> _parse_revision_str('234')
77
 
    [<RevisionSpec_int 234>]
78
 
    >>> _parse_revision_str('234..567')
79
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 567>]
80
 
    >>> _parse_revision_str('..')
81
 
    [<RevisionSpec None>, <RevisionSpec None>]
82
 
    >>> _parse_revision_str('..234')
83
 
    [<RevisionSpec None>, <RevisionSpec_int 234>]
84
 
    >>> _parse_revision_str('234..')
85
 
    [<RevisionSpec_int 234>, <RevisionSpec None>]
86
 
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
87
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
88
 
    >>> _parse_revision_str('234....789') #Error ?
89
 
    [<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
90
 
    >>> _parse_revision_str('revid:test@other.com-234234')
91
 
    [<RevisionSpec_revid revid:test@other.com-234234>]
92
 
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
93
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
94
 
    >>> _parse_revision_str('revid:test@other.com-234234..23')
95
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
96
 
    >>> _parse_revision_str('date:2005-04-12')
97
 
    [<RevisionSpec_date date:2005-04-12>]
98
 
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
99
 
    [<RevisionSpec_date date:2005-04-12 12:24:33>]
100
 
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
101
 
    [<RevisionSpec_date date:2005-04-12T12:24:33>]
102
 
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
103
 
    [<RevisionSpec_date date:2005-04-12,12:24:33>]
104
 
    >>> _parse_revision_str('-5..23')
105
 
    [<RevisionSpec_int -5>, <RevisionSpec_int 23>]
106
 
    >>> _parse_revision_str('-5')
107
 
    [<RevisionSpec_int -5>]
108
 
    >>> _parse_revision_str('123a')
109
 
    Traceback (most recent call last):
110
 
      ...
111
 
    BzrError: No namespace registered for string: '123a'
112
 
    >>> _parse_revision_str('abc')
113
 
    Traceback (most recent call last):
114
 
      ...
115
 
    BzrError: No namespace registered for string: 'abc'
116
 
    >>> _parse_revision_str('branch:../branch2')
117
 
    [<RevisionSpec_branch branch:../branch2>]
118
 
    """
119
 
    import re
120
 
    old_format_re = re.compile('\d*:\d*')
121
 
    m = old_format_re.match(revstr)
122
 
    revs = []
123
 
    if m:
124
 
        warning('Colon separator for revision numbers is deprecated.'
125
 
                ' Use .. instead')
126
 
        for rev in revstr.split(':'):
127
 
            if rev:
128
 
                revs.append(RevisionSpec(int(rev)))
129
 
            else:
130
 
                revs.append(RevisionSpec(None))
131
 
    else:
132
 
        next_prefix = None
133
 
        for x in revstr.split('..'):
134
 
            if not x:
135
 
                revs.append(RevisionSpec(None))
136
 
            elif x[-1] == ':':
137
 
                # looks like a namespace:.. has happened
138
 
                next_prefix = x + '..'
139
 
            else:
140
 
                if next_prefix is not None:
141
 
                    x = next_prefix + x
142
 
                revs.append(RevisionSpec(x))
143
 
                next_prefix = None
144
 
        if next_prefix is not None:
145
 
            revs.append(RevisionSpec(next_prefix))
146
 
    return revs
147
 
 
148
 
 
149
 
def _builtin_commands():
150
 
    import bzrlib.builtins
151
 
    r = {}
152
 
    builtins = bzrlib.builtins.__dict__
153
 
    for name in builtins:
154
 
        if name.startswith("cmd_"):
155
 
            real_name = _unsquish_command_name(name)        
156
 
            r[real_name] = builtins[name]
157
 
    return r
158
 
 
159
 
            
160
 
 
161
 
def builtin_command_names():
162
 
    """Return list of builtin command names."""
163
 
    return _builtin_commands().keys()
164
 
    
165
 
 
166
 
def plugin_command_names():
167
 
    return plugin_cmds.keys()
168
 
 
169
 
 
170
 
def _get_cmd_dict(plugins_override=True):
171
 
    """Return name->class mapping for all commands."""
172
 
    d = _builtin_commands()
173
 
    if plugins_override:
174
 
        d.update(plugin_cmds)
175
 
    return d
176
 
 
177
 
    
178
 
def get_all_cmds(plugins_override=True):
179
 
    """Return canonical name and class for all registered commands."""
180
 
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
181
 
        yield k,v
182
 
 
183
 
 
184
 
def get_cmd_object(cmd_name, plugins_override=True):
185
 
    """Return the canonical name and command class for a command.
186
 
 
187
 
    plugins_override
188
 
        If true, plugin commands can override builtins.
189
 
    """
190
 
    from bzrlib.externalcommand import ExternalCommand
191
 
 
192
 
    cmd_name = str(cmd_name)            # not unicode
193
 
 
194
 
    # first look up this command under the specified name
195
 
    cmds = _get_cmd_dict(plugins_override=plugins_override)
196
 
    try:
197
 
        return cmds[cmd_name]()
198
 
    except KeyError:
199
 
        pass
200
 
 
201
 
    # look for any command which claims this as an alias
202
 
    for real_cmd_name, cmd_class in cmds.iteritems():
203
 
        if cmd_name in cmd_class.aliases:
204
 
            return cmd_class()
205
 
 
206
 
    cmd_obj = ExternalCommand.find_command(cmd_name)
207
 
    if cmd_obj:
208
 
        return cmd_obj
209
 
 
210
 
    raise BzrCommandError("unknown command %r" % cmd_name)
211
 
 
212
 
 
213
 
class Command(object):
214
 
    """Base class for commands.
215
 
 
216
 
    Commands are the heart of the command-line bzr interface.
217
 
 
218
 
    The command object mostly handles the mapping of command-line
219
 
    parameters into one or more bzrlib operations, and of the results
220
 
    into textual output.
221
 
 
222
 
    Commands normally don't have any state.  All their arguments are
223
 
    passed in to the run method.  (Subclasses may take a different
224
 
    policy if the behaviour of the instance needs to depend on e.g. a
225
 
    shell plugin and not just its Python class.)
226
 
 
227
 
    The docstring for an actual command should give a single-line
228
 
    summary, then a complete description of the command.  A grammar
229
 
    description will be inserted.
230
 
 
231
 
    aliases
232
 
        Other accepted names for this command.
233
 
 
234
 
    takes_args
235
 
        List of argument forms, marked with whether they are optional,
236
 
        repeated, etc.
237
 
 
238
 
    takes_options
239
 
        List of options that may be given for this command.
240
 
 
241
 
    hidden
242
 
        If true, this command isn't advertised.  This is typically
243
 
        for commands intended for expert users.
244
 
    """
245
 
    aliases = []
246
 
    
247
 
    takes_args = []
248
 
    takes_options = []
249
 
 
250
 
    hidden = False
251
 
    
252
 
    def __init__(self):
253
 
        """Construct an instance of this command."""
254
 
        if self.__doc__ == Command.__doc__:
255
 
            warn("No help message set for %r" % self)
256
 
 
257
 
 
258
 
    def run_argv(self, argv):
259
 
        """Parse command line and run."""
260
 
        args, opts = parse_args(argv)
261
 
 
262
 
        if 'help' in opts:  # e.g. bzr add --help
263
 
            from bzrlib.help import help_on_command
264
 
            help_on_command(self.name())
265
 
            return 0
266
 
 
267
 
        # check options are reasonable
268
 
        allowed = self.takes_options
269
 
        for oname in opts:
270
 
            if oname not in allowed:
271
 
                raise BzrCommandError("option '--%s' is not allowed for command %r"
272
 
                                      % (oname, self.name()))
273
 
 
274
 
        # mix arguments and options into one dictionary
275
 
        cmdargs = _match_argform(self.name(), self.takes_args, args)
276
 
        cmdopts = {}
277
 
        for k, v in opts.items():
278
 
            cmdopts[k.replace('-', '_')] = v
279
 
 
280
 
        all_cmd_args = cmdargs.copy()
281
 
        all_cmd_args.update(cmdopts)
282
 
 
283
 
        return self.run(**all_cmd_args)
284
 
 
285
 
    
286
 
    def run(self):
287
 
        """Actually run the command.
288
 
 
289
 
        This is invoked with the options and arguments bound to
290
 
        keyword parameters.
291
 
 
292
 
        Return 0 or None if the command was successful, or a non-zero
293
 
        shell error code if not.  It's OK for this method to allow
294
 
        an exception to raise up.
295
 
        """
296
 
        raise NotImplementedError()
297
 
 
298
 
 
299
 
    def help(self):
300
 
        """Return help message for this class."""
301
 
        if self.__doc__ is Command.__doc__:
302
 
            return None
303
 
        return getdoc(self)
304
 
 
305
 
    def name(self):
306
 
        return _unsquish_command_name(self.__class__.__name__)
307
 
 
308
 
 
309
 
def parse_spec(spec):
310
 
    """
311
 
    >>> parse_spec(None)
312
 
    [None, None]
313
 
    >>> parse_spec("./")
314
 
    ['./', None]
315
 
    >>> parse_spec("../@")
316
 
    ['..', -1]
317
 
    >>> parse_spec("../f/@35")
318
 
    ['../f', 35]
319
 
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
320
 
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
321
 
    """
322
 
    if spec is None:
323
 
        return [None, None]
324
 
    if '/@' in spec:
325
 
        parsed = spec.split('/@')
326
 
        assert len(parsed) == 2
327
 
        if parsed[1] == "":
328
 
            parsed[1] = -1
329
 
        else:
330
 
            try:
331
 
                parsed[1] = int(parsed[1])
332
 
            except ValueError:
333
 
                pass # We can allow stuff like ./@revid:blahblahblah
334
 
            else:
335
 
                assert parsed[1] >=0
336
 
    else:
337
 
        parsed = [spec, None]
338
 
    return parsed
339
 
 
340
 
 
341
 
# list of all available options; the rhs can be either None for an
342
 
# option that takes no argument, or a constructor function that checks
343
 
# the type.
344
 
OPTIONS = {
345
 
    'all':                    None,
346
 
    'basis':                  str,
347
 
    'diff-options':           str,
348
 
    'help':                   None,
349
 
    'file':                   unicode,
350
 
    'force':                  None,
351
 
    'format':                 unicode,
352
 
    'forward':                None,
353
 
    'quiet':                  None,
354
 
    'message':                unicode,
355
 
    'no-recurse':             None,
356
 
    'profile':                None,
357
 
    'revision':               _parse_revision_str,
358
 
    'short':                  None,
359
 
    'show-ids':               None,
360
 
    'timezone':               str,
361
 
    'verbose':                None,
362
 
    'version':                None,
363
 
    'email':                  None,
364
 
    'unchanged':              None,
365
 
    'update':                 None,
366
 
    'long':                   None,
367
 
    'root':                   str,
368
 
    'no-backup':              None,
369
 
    'pattern':                str,
370
 
    'remember':               None,
371
 
    }
372
 
 
373
 
SHORT_OPTIONS = {
374
 
    'F':                      'file', 
375
 
    'h':                      'help',
376
 
    'm':                      'message',
377
 
    'r':                      'revision',
378
 
    'v':                      'verbose',
379
 
    'l':                      'long',
380
 
    'q':                      'quiet',
381
 
}
382
 
 
383
 
 
384
 
def parse_args(argv):
385
 
    """Parse command line.
386
 
    
387
 
    Arguments and options are parsed at this level before being passed
388
 
    down to specific command handlers.  This routine knows, from a
389
 
    lookup table, something about the available options, what optargs
390
 
    they take, and which commands will accept them.
391
 
 
392
 
    >>> parse_args('--help'.split())
393
 
    ([], {'help': True})
394
 
    >>> parse_args('help -- --invalidcmd'.split())
395
 
    (['help', '--invalidcmd'], {})
396
 
    >>> parse_args('--version'.split())
397
 
    ([], {'version': True})
398
 
    >>> parse_args('status --all'.split())
399
 
    (['status'], {'all': True})
400
 
    >>> parse_args('commit --message=biter'.split())
401
 
    (['commit'], {'message': u'biter'})
402
 
    >>> parse_args('log -r 500'.split())
403
 
    (['log'], {'revision': [<RevisionSpec_int 500>]})
404
 
    >>> parse_args('log -r500..600'.split())
405
 
    (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
406
 
    >>> parse_args('log -vr500..600'.split())
407
 
    (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
408
 
    >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
409
 
    (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
410
 
    """
411
 
    args = []
412
 
    opts = {}
413
 
 
414
 
    argsover = False
415
 
    while argv:
416
 
        a = argv.pop(0)
417
 
        if not argsover and a[0] == '-':
418
 
            # option names must not be unicode
419
 
            a = str(a)
420
 
            optarg = None
421
 
            if a[1] == '-':
422
 
                if a == '--':
423
 
                    # We've received a standalone -- No more flags
424
 
                    argsover = True
425
 
                    continue
426
 
                mutter("  got option %r" % a)
427
 
                if '=' in a:
428
 
                    optname, optarg = a[2:].split('=', 1)
429
 
                else:
430
 
                    optname = a[2:]
431
 
                if optname not in OPTIONS:
432
 
                    raise BzrError('unknown long option %r' % a)
433
 
            else:
434
 
                shortopt = a[1:]
435
 
                if shortopt in SHORT_OPTIONS:
436
 
                    # Multi-character options must have a space to delimit
437
 
                    # their value
438
 
                    optname = SHORT_OPTIONS[shortopt]
439
 
                else:
440
 
                    # Single character short options, can be chained,
441
 
                    # and have their value appended to their name
442
 
                    shortopt = a[1:2]
443
 
                    if shortopt not in SHORT_OPTIONS:
444
 
                        # We didn't find the multi-character name, and we
445
 
                        # didn't find the single char name
446
 
                        raise BzrError('unknown short option %r' % a)
447
 
                    optname = SHORT_OPTIONS[shortopt]
448
 
 
449
 
                    if a[2:]:
450
 
                        # There are extra things on this option
451
 
                        # see if it is the value, or if it is another
452
 
                        # short option
453
 
                        optargfn = OPTIONS[optname]
454
 
                        if optargfn is None:
455
 
                            # This option does not take an argument, so the
456
 
                            # next entry is another short option, pack it back
457
 
                            # into the list
458
 
                            argv.insert(0, '-' + a[2:])
459
 
                        else:
460
 
                            # This option takes an argument, so pack it
461
 
                            # into the array
462
 
                            optarg = a[2:]
463
 
            
464
 
            if optname in opts:
465
 
                # XXX: Do we ever want to support this, e.g. for -r?
466
 
                raise BzrError('repeated option %r' % a)
467
 
                
468
 
            optargfn = OPTIONS[optname]
469
 
            if optargfn:
470
 
                if optarg == None:
471
 
                    if not argv:
472
 
                        raise BzrError('option %r needs an argument' % a)
473
 
                    else:
474
 
                        optarg = argv.pop(0)
475
 
                opts[optname] = optargfn(optarg)
476
 
            else:
477
 
                if optarg != None:
478
 
                    raise BzrError('option %r takes no argument' % optname)
479
 
                opts[optname] = True
480
 
        else:
481
 
            args.append(a)
482
 
 
483
 
    return args, opts
484
 
 
485
 
 
486
 
 
487
 
 
488
 
def _match_argform(cmd, takes_args, args):
489
 
    argdict = {}
490
 
 
491
 
    # step through args and takes_args, allowing appropriate 0-many matches
492
 
    for ap in takes_args:
493
 
        argname = ap[:-1]
494
 
        if ap[-1] == '?':
495
 
            if args:
496
 
                argdict[argname] = args.pop(0)
497
 
        elif ap[-1] == '*': # all remaining arguments
498
 
            if args:
499
 
                argdict[argname + '_list'] = args[:]
500
 
                args = []
501
 
            else:
502
 
                argdict[argname + '_list'] = None
503
 
        elif ap[-1] == '+':
504
 
            if not args:
505
 
                raise BzrCommandError("command %r needs one or more %s"
506
 
                        % (cmd, argname.upper()))
507
 
            else:
508
 
                argdict[argname + '_list'] = args[:]
509
 
                args = []
510
 
        elif ap[-1] == '$': # all but one
511
 
            if len(args) < 2:
512
 
                raise BzrCommandError("command %r needs one or more %s"
513
 
                        % (cmd, argname.upper()))
514
 
            argdict[argname + '_list'] = args[:-1]
515
 
            args[:-1] = []                
516
 
        else:
517
 
            # just a plain arg
518
 
            argname = ap
519
 
            if not args:
520
 
                raise BzrCommandError("command %r requires argument %s"
521
 
                        % (cmd, argname.upper()))
522
 
            else:
523
 
                argdict[argname] = args.pop(0)
524
 
            
525
 
    if args:
526
 
        raise BzrCommandError("extra argument to command %s: %s"
527
 
                              % (cmd, args[0]))
528
 
 
529
 
    return argdict
530
 
 
531
 
 
532
 
 
533
 
def apply_profiled(the_callable, *args, **kwargs):
534
 
    import hotshot
535
 
    import tempfile
536
 
    import hotshot.stats
537
 
    pffileno, pfname = tempfile.mkstemp()
538
 
    try:
539
 
        prof = hotshot.Profile(pfname)
540
 
        try:
541
 
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
542
 
        finally:
543
 
            prof.close()
544
 
        stats = hotshot.stats.load(pfname)
545
 
        stats.strip_dirs()
546
 
        stats.sort_stats('cum')   # 'time'
547
 
        ## XXX: Might like to write to stderr or the trace file instead but
548
 
        ## print_stats seems hardcoded to stdout
549
 
        stats.print_stats(20)
550
 
        return ret
551
 
    finally:
552
 
        os.close(pffileno)
553
 
        os.remove(pfname)
554
 
 
555
 
 
556
 
def run_bzr(argv):
557
 
    """Execute a command.
558
 
 
559
 
    This is similar to main(), but without all the trappings for
560
 
    logging and error handling.  
561
 
    
562
 
    argv
563
 
       The command-line arguments, without the program name from argv[0]
564
 
    
565
 
    Returns a command status or raises an exception.
566
 
 
567
 
    Special master options: these must come before the command because
568
 
    they control how the command is interpreted.
569
 
 
570
 
    --no-plugins
571
 
        Do not load plugin modules at all
572
 
 
573
 
    --builtin
574
 
        Only use builtin commands.  (Plugins are still allowed to change
575
 
        other behaviour.)
576
 
 
577
 
    --profile
578
 
        Run under the Python profiler.
579
 
    """
580
 
    # Load all of the transport methods
581
 
    import bzrlib.transport.local, bzrlib.transport.http
582
 
    
583
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
584
 
 
585
 
    opt_profile = opt_no_plugins = opt_builtin = False
586
 
 
587
 
    # --no-plugins is handled specially at a very early stage. We need
588
 
    # to load plugins before doing other command parsing so that they
589
 
    # can override commands, but this needs to happen first.
590
 
 
591
 
    for a in argv:
592
 
        if a == '--profile':
593
 
            opt_profile = True
594
 
        elif a == '--no-plugins':
595
 
            opt_no_plugins = True
596
 
        elif a == '--builtin':
597
 
            opt_builtin = True
598
 
        else:
599
 
            break
600
 
        argv.remove(a)
601
 
 
602
 
    if (not argv) or (argv[0] == '--help'):
603
 
        from bzrlib.help import help
604
 
        if len(argv) > 1:
605
 
            help(argv[1])
606
 
        else:
607
 
            help()
608
 
        return 0
609
 
 
610
 
    if argv[0] == '--version':
611
 
        from bzrlib.builtins import show_version
612
 
        show_version()
613
 
        return 0
614
 
        
615
 
    if not opt_no_plugins:
616
 
        from bzrlib.plugin import load_plugins
617
 
        load_plugins()
618
 
 
619
 
    cmd = str(argv.pop(0))
620
 
 
621
 
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
622
 
 
623
 
    if opt_profile:
624
 
        ret = apply_profiled(cmd_obj.run_argv, argv)
625
 
    else:
626
 
        ret = cmd_obj.run_argv(argv)
627
 
    return ret or 0
628
 
 
629
 
 
630
 
def main(argv):
631
 
    import bzrlib.ui
632
 
    bzrlib.trace.log_startup(argv)
633
 
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
634
 
 
635
 
    return run_bzr_catch_errors(argv[1:])
636
 
 
637
 
 
638
 
def run_bzr_catch_errors(argv):
639
 
    try:
640
 
        try:
641
 
            return run_bzr(argv)
642
 
        finally:
643
 
            # do this here inside the exception wrappers to catch EPIPE
644
 
            sys.stdout.flush()
645
 
    except BzrCommandError, e:
646
 
        # command line syntax error, etc
647
 
        log_error(str(e))
648
 
        return 1
649
 
    except BzrError, e:
650
 
        bzrlib.trace.log_exception()
651
 
        return 1
652
 
    except AssertionError, e:
653
 
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
654
 
        return 3
655
 
    except KeyboardInterrupt, e:
656
 
        bzrlib.trace.log_exception('interrupted')
657
 
        return 2
658
 
    except Exception, e:
659
 
        import errno
660
 
        if (isinstance(e, IOError) 
661
 
            and hasattr(e, 'errno')
662
 
            and e.errno == errno.EPIPE):
663
 
            bzrlib.trace.note('broken pipe')
664
 
            return 2
665
 
        else:
666
 
            ## import pdb
667
 
            ## pdb.pm()
668
 
            bzrlib.trace.log_exception()
669
 
            return 2
670
 
 
671
 
if __name__ == '__main__':
672
 
    sys.exit(main(sys.argv))