/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-09-01 11:27:20 UTC
  • Revision ID: mbp@sourcefrog.net-20050901112720-f5ccb6b6627991de
- work properly when $EDITOR contains multiple words

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
 
 
28
 
 
29
import sys
 
30
import os
 
31
from warnings import warn
 
32
from inspect import getdoc
 
33
 
 
34
import bzrlib
 
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
 
40
 
 
41
plugin_cmds = {}
 
42
 
 
43
 
 
44
def register_command(cmd):
 
45
    "Utility function to help register a command"
 
46
    global plugin_cmds
 
47
    k = cmd.__name__
 
48
    if k.startswith("cmd_"):
 
49
        k_unsquished = _unsquish_command_name(k)
 
50
    else:
 
51
        k_unsquished = k
 
52
    if not plugin_cmds.has_key(k_unsquished):
 
53
        plugin_cmds[k_unsquished] = cmd
 
54
        mutter('registered plugin command %s', k_unsquished)      
 
55
    else:
 
56
        log_error('Two plugins defined the same command: %r' % k)
 
57
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
58
 
 
59
 
 
60
def _squish_command_name(cmd):
 
61
    return 'cmd_' + cmd.replace('-', '_')
 
62
 
 
63
 
 
64
def _unsquish_command_name(cmd):
 
65
    assert cmd.startswith("cmd_")
 
66
    return cmd[4:].replace('_','-')
 
67
 
 
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
 
 
167
def _builtin_commands():
 
168
    import bzrlib.builtins
 
169
    r = {}
 
170
    builtins = bzrlib.builtins.__dict__
 
171
    for name in builtins:
 
172
        if name.startswith("cmd_"):
 
173
            real_name = _unsquish_command_name(name)        
 
174
            r[real_name] = builtins[name]
 
175
    return r
 
176
 
 
177
            
 
178
 
 
179
def builtin_command_names():
 
180
    """Return list of builtin command names."""
 
181
    return _builtin_commands().keys()
 
182
    
 
183
 
 
184
def plugin_command_names():
 
185
    return plugin_cmds.keys()
 
186
 
 
187
 
 
188
def _get_cmd_dict(plugins_override=True):
 
189
    """Return name->class mapping for all commands."""
 
190
    d = _builtin_commands()
 
191
    if plugins_override:
 
192
        d.update(plugin_cmds)
 
193
    return d
 
194
 
 
195
    
 
196
def get_all_cmds(plugins_override=True):
 
197
    """Return canonical name and class for all registered commands."""
 
198
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
199
        yield k,v
 
200
 
 
201
 
 
202
def get_cmd_object(cmd_name, plugins_override=True):
 
203
    """Return the canonical name and command class for a command.
 
204
 
 
205
    plugins_override
 
206
        If true, plugin commands can override builtins.
 
207
    """
 
208
    from bzrlib.externalcommand import ExternalCommand
 
209
 
 
210
    cmd_name = str(cmd_name)            # not unicode
 
211
 
 
212
    # first look up this command under the specified name
 
213
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
214
    try:
 
215
        return cmds[cmd_name]()
 
216
    except KeyError:
 
217
        pass
 
218
 
 
219
    # look for any command which claims this as an alias
 
220
    for real_cmd_name, cmd_class in cmds.iteritems():
 
221
        if cmd_name in cmd_class.aliases:
 
222
            return cmd_class()
 
223
 
 
224
    cmd_obj = ExternalCommand.find_command(cmd_name)
 
225
    if cmd_obj:
 
226
        return cmd_obj
 
227
 
 
228
    raise BzrCommandError("unknown command %r" % cmd_name)
 
229
 
 
230
 
 
231
class Command(object):
 
232
    """Base class for commands.
 
233
 
 
234
    Commands are the heart of the command-line bzr interface.
 
235
 
 
236
    The command object mostly handles the mapping of command-line
 
237
    parameters into one or more bzrlib operations, and of the results
 
238
    into textual output.
 
239
 
 
240
    Commands normally don't have any state.  All their arguments are
 
241
    passed in to the run method.  (Subclasses may take a different
 
242
    policy if the behaviour of the instance needs to depend on e.g. a
 
243
    shell plugin and not just its Python class.)
 
244
 
 
245
    The docstring for an actual command should give a single-line
 
246
    summary, then a complete description of the command.  A grammar
 
247
    description will be inserted.
 
248
 
 
249
    aliases
 
250
        Other accepted names for this command.
 
251
 
 
252
    takes_args
 
253
        List of argument forms, marked with whether they are optional,
 
254
        repeated, etc.
 
255
 
 
256
    takes_options
 
257
        List of options that may be given for this command.
 
258
 
 
259
    hidden
 
260
        If true, this command isn't advertised.  This is typically
 
261
        for commands intended for expert users.
 
262
    """
 
263
    aliases = []
 
264
    
 
265
    takes_args = []
 
266
    takes_options = []
 
267
 
 
268
    hidden = False
 
269
    
 
270
    def __init__(self):
 
271
        """Construct an instance of this command."""
 
272
        if self.__doc__ == Command.__doc__:
 
273
            warn("No help message set for %r" % self)
 
274
 
 
275
 
 
276
    def run_argv(self, argv):
 
277
        """Parse command line and run."""
 
278
        args, opts = parse_args(argv)
 
279
 
 
280
        if 'help' in opts:  # e.g. bzr add --help
 
281
            from bzrlib.help import help_on_command
 
282
            help_on_command(self.name())
 
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
 
 
292
        # mix arguments and options into one dictionary
 
293
        cmdargs = _match_argform(self.name(), self.takes_args, args)
 
294
        cmdopts = {}
 
295
        for k, v in opts.items():
 
296
            cmdopts[k.replace('-', '_')] = v
 
297
 
 
298
        all_cmd_args = cmdargs.copy()
 
299
        all_cmd_args.update(cmdopts)
 
300
 
 
301
        return self.run(**all_cmd_args)
 
302
 
 
303
    
 
304
    def run(self):
 
305
        """Actually run the command.
 
306
 
 
307
        This is invoked with the options and arguments bound to
 
308
        keyword parameters.
 
309
 
 
310
        Return 0 or None if the command was successful, or a non-zero
 
311
        shell error code if not.  It's OK for this method to allow
 
312
        an exception to raise up.
 
313
        """
 
314
        raise NotImplementedError()
 
315
 
 
316
 
 
317
    def help(self):
 
318
        """Return help message for this class."""
 
319
        if self.__doc__ is Command.__doc__:
 
320
            return None
 
321
        return getdoc(self)
 
322
 
 
323
    def name(self):
 
324
        return _unsquish_command_name(self.__class__.__name__)
 
325
 
 
326
 
 
327
def parse_spec(spec):
 
328
    """
 
329
    >>> parse_spec(None)
 
330
    [None, None]
 
331
    >>> parse_spec("./")
 
332
    ['./', None]
 
333
    >>> parse_spec("../@")
 
334
    ['..', -1]
 
335
    >>> parse_spec("../f/@35")
 
336
    ['../f', 35]
 
337
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
338
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
 
339
    """
 
340
    if spec is None:
 
341
        return [None, None]
 
342
    if '/@' in spec:
 
343
        parsed = spec.split('/@')
 
344
        assert len(parsed) == 2
 
345
        if parsed[1] == "":
 
346
            parsed[1] = -1
 
347
        else:
 
348
            try:
 
349
                parsed[1] = int(parsed[1])
 
350
            except ValueError:
 
351
                pass # We can allow stuff like ./@revid:blahblahblah
 
352
            else:
 
353
                assert parsed[1] >=0
 
354
    else:
 
355
        parsed = [spec, None]
 
356
    return parsed
 
357
 
 
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):
 
402
    """Parse command line.
 
403
    
 
404
    Arguments and options are parsed at this level before being passed
 
405
    down to specific command handlers.  This routine knows, from a
 
406
    lookup table, something about the available options, what optargs
 
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]})
 
427
    """
 
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
 
 
500
    return args, opts
 
501
 
 
502
 
 
503
 
 
504
 
 
505
def _match_argform(cmd, takes_args, args):
 
506
    argdict = {}
 
507
 
 
508
    # step through args and takes_args, allowing appropriate 0-many matches
 
509
    for ap in takes_args:
 
510
        argname = ap[:-1]
 
511
        if ap[-1] == '?':
 
512
            if args:
 
513
                argdict[argname] = args.pop(0)
 
514
        elif ap[-1] == '*': # all remaining arguments
 
515
            if args:
 
516
                argdict[argname + '_list'] = args[:]
 
517
                args = []
 
518
            else:
 
519
                argdict[argname + '_list'] = None
 
520
        elif ap[-1] == '+':
 
521
            if not args:
 
522
                raise BzrCommandError("command %r needs one or more %s"
 
523
                        % (cmd, argname.upper()))
 
524
            else:
 
525
                argdict[argname + '_list'] = args[:]
 
526
                args = []
 
527
        elif ap[-1] == '$': # all but one
 
528
            if len(args) < 2:
 
529
                raise BzrCommandError("command %r needs one or more %s"
 
530
                        % (cmd, argname.upper()))
 
531
            argdict[argname + '_list'] = args[:-1]
 
532
            args[:-1] = []                
 
533
        else:
 
534
            # just a plain arg
 
535
            argname = ap
 
536
            if not args:
 
537
                raise BzrCommandError("command %r requires argument %s"
 
538
                        % (cmd, argname.upper()))
 
539
            else:
 
540
                argdict[argname] = args.pop(0)
 
541
            
 
542
    if args:
 
543
        raise BzrCommandError("extra argument to command %s: %s"
 
544
                              % (cmd, args[0]))
 
545
 
 
546
    return argdict
 
547
 
 
548
 
 
549
 
 
550
def apply_profiled(the_callable, *args, **kwargs):
 
551
    import hotshot
 
552
    import tempfile
 
553
    pffileno, pfname = tempfile.mkstemp()
 
554
    try:
 
555
        prof = hotshot.Profile(pfname)
 
556
        try:
 
557
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
 
558
        finally:
 
559
            prof.close()
 
560
 
 
561
        import hotshot.stats
 
562
        stats = hotshot.stats.load(pfname)
 
563
        #stats.strip_dirs()
 
564
        stats.sort_stats('time')
 
565
        ## XXX: Might like to write to stderr or the trace file instead but
 
566
        ## print_stats seems hardcoded to stdout
 
567
        stats.print_stats(20)
 
568
 
 
569
        return ret
 
570
    finally:
 
571
        os.close(pffileno)
 
572
        os.remove(pfname)
 
573
 
 
574
 
 
575
def run_bzr(argv):
 
576
    """Execute a command.
 
577
 
 
578
    This is similar to main(), but without all the trappings for
 
579
    logging and error handling.  
 
580
    
 
581
    argv
 
582
       The command-line arguments, without the program name from argv[0]
 
583
    
 
584
    Returns a command status or raises an exception.
 
585
 
 
586
    Special master options: these must come before the command because
 
587
    they control how the command is interpreted.
 
588
 
 
589
    --no-plugins
 
590
        Do not load plugin modules at all
 
591
 
 
592
    --builtin
 
593
        Only use builtin commands.  (Plugins are still allowed to change
 
594
        other behaviour.)
 
595
 
 
596
    --profile
 
597
        Run under the Python profiler.
 
598
    """
 
599
    
 
600
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
601
 
 
602
    opt_profile = opt_no_plugins = opt_builtin = False
 
603
 
 
604
    # --no-plugins is handled specially at a very early stage. We need
 
605
    # to load plugins before doing other command parsing so that they
 
606
    # can override commands, but this needs to happen first.
 
607
 
 
608
    for a in argv:
 
609
        if a == '--profile':
 
610
            opt_profile = True
 
611
        elif a == '--no-plugins':
 
612
            opt_no_plugins = True
 
613
        elif a == '--builtin':
 
614
            opt_builtin = True
 
615
        else:
 
616
            break
 
617
        argv.remove(a)
 
618
 
 
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()
 
625
        return 0
 
626
 
 
627
    if argv[0] == '--version':
 
628
        from bzrlib.builtins import show_version
 
629
        show_version()
 
630
        return 0
 
631
        
 
632
    if not opt_no_plugins:
 
633
        from bzrlib.plugin import load_plugins
 
634
        load_plugins()
 
635
 
 
636
    cmd = str(argv.pop(0))
 
637
 
 
638
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
 
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
 
645
 
 
646
 
 
647
def main(argv):
 
648
    import bzrlib.ui
 
649
    bzrlib.trace.log_startup(argv)
 
650
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
651
 
 
652
    try:
 
653
        try:
 
654
            return run_bzr(argv[1:])
 
655
        finally:
 
656
            # do this here inside the exception wrappers to catch EPIPE
 
657
            sys.stdout.flush()
 
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))
 
667
        return 3
 
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
 
678
        else:
 
679
            bzrlib.trace.log_exception()
 
680
            return 2
 
681
 
 
682
 
 
683
if __name__ == '__main__':
 
684
    sys.exit(main(sys.argv))