~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-08-30 06:10:39 UTC
  • Revision ID: mbp@sourcefrog.net-20050830061039-1d0347fb236c39ad
- clean up some code in revision.py

- move all exceptions to bzrlib.errors

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
# would help with validation and shell completion.
26
26
 
27
27
 
28
 
# TODO: Help messages for options.
29
 
 
30
 
# TODO: Define arguments by objects, rather than just using names.
31
 
# Those objects can specify the expected type of the argument, which
32
 
# would help with validation and shell completion.
33
 
 
34
 
 
35
 
 
36
28
import sys
37
29
import os
38
 
from warnings import warn
39
 
from inspect import getdoc
40
30
 
41
31
import bzrlib
42
32
import bzrlib.trace
43
33
from bzrlib.trace import mutter, note, log_error, warning
44
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
45
 
from bzrlib.revisionspec import RevisionSpec
 
34
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
 
35
from bzrlib.branch import find_branch
46
36
from bzrlib import BZRDIR
47
37
 
 
38
 
48
39
plugin_cmds = {}
49
40
 
50
41
 
76
67
def _parse_revision_str(revstr):
77
68
    """This handles a revision string -> revno.
78
69
 
79
 
    This always returns a list.  The list will have one element for
80
 
    each revision.
 
70
    This always returns a list.  The list will have one element for 
 
71
 
 
72
    It supports integers directly, but everything else it
 
73
    defers for passing to Branch.get_revision_info()
81
74
 
82
75
    >>> _parse_revision_str('234')
83
 
    [<RevisionSpec_int 234>]
 
76
    [234]
84
77
    >>> _parse_revision_str('234..567')
85
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 567>]
 
78
    [234, 567]
86
79
    >>> _parse_revision_str('..')
87
 
    [<RevisionSpec None>, <RevisionSpec None>]
 
80
    [None, None]
88
81
    >>> _parse_revision_str('..234')
89
 
    [<RevisionSpec None>, <RevisionSpec_int 234>]
 
82
    [None, 234]
90
83
    >>> _parse_revision_str('234..')
91
 
    [<RevisionSpec_int 234>, <RevisionSpec None>]
 
84
    [234, None]
92
85
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
93
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
 
86
    [234, 456, 789]
94
87
    >>> _parse_revision_str('234....789') # Error?
95
 
    [<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
 
88
    [234, None, 789]
96
89
    >>> _parse_revision_str('revid:test@other.com-234234')
97
 
    [<RevisionSpec_revid revid:test@other.com-234234>]
 
90
    ['revid:test@other.com-234234']
98
91
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
99
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
 
92
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
100
93
    >>> _parse_revision_str('revid:test@other.com-234234..23')
101
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
 
94
    ['revid:test@other.com-234234', 23]
102
95
    >>> _parse_revision_str('date:2005-04-12')
103
 
    [<RevisionSpec_date date:2005-04-12>]
 
96
    ['date:2005-04-12']
104
97
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
105
 
    [<RevisionSpec_date date:2005-04-12 12:24:33>]
 
98
    ['date:2005-04-12 12:24:33']
106
99
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
107
 
    [<RevisionSpec_date date:2005-04-12T12:24:33>]
 
100
    ['date:2005-04-12T12:24:33']
108
101
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
109
 
    [<RevisionSpec_date date:2005-04-12,12:24:33>]
 
102
    ['date:2005-04-12,12:24:33']
110
103
    >>> _parse_revision_str('-5..23')
111
 
    [<RevisionSpec_int -5>, <RevisionSpec_int 23>]
 
104
    [-5, 23]
112
105
    >>> _parse_revision_str('-5')
113
 
    [<RevisionSpec_int -5>]
 
106
    [-5]
114
107
    >>> _parse_revision_str('123a')
115
 
    Traceback (most recent call last):
116
 
      ...
117
 
    BzrError: No namespace registered for string: '123a'
 
108
    ['123a']
118
109
    >>> _parse_revision_str('abc')
119
 
    Traceback (most recent call last):
120
 
      ...
121
 
    BzrError: No namespace registered for string: 'abc'
 
110
    ['abc']
122
111
    """
123
112
    import re
124
113
    old_format_re = re.compile('\d*:\d*')
125
114
    m = old_format_re.match(revstr)
126
 
    revs = []
127
115
    if m:
128
116
        warning('Colon separator for revision numbers is deprecated.'
129
117
                ' Use .. instead')
 
118
        revs = []
130
119
        for rev in revstr.split(':'):
131
120
            if rev:
132
 
                revs.append(RevisionSpec(int(rev)))
133
 
            else:
134
 
                revs.append(RevisionSpec(None))
135
 
    else:
136
 
        for x in revstr.split('..'):
137
 
            if not x:
138
 
                revs.append(RevisionSpec(None))
139
 
            else:
140
 
                revs.append(RevisionSpec(x))
 
121
                revs.append(int(rev))
 
122
            else:
 
123
                revs.append(None)
 
124
        return revs
 
125
    revs = []
 
126
    for x in revstr.split('..'):
 
127
        if not x:
 
128
            revs.append(None)
 
129
        else:
 
130
            try:
 
131
                revs.append(int(x))
 
132
            except ValueError:
 
133
                revs.append(x)
141
134
    return revs
142
135
 
143
136
 
144
 
def _builtin_commands():
 
137
def get_merge_type(typestring):
 
138
    """Attempt to find the merge class/factory associated with a string."""
 
139
    from merge import merge_types
 
140
    try:
 
141
        return merge_types[typestring][0]
 
142
    except KeyError:
 
143
        templ = '%s%%7s: %%s' % (' '*12)
 
144
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
145
        type_list = '\n'.join(lines)
 
146
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
147
            (typestring, type_list)
 
148
        raise BzrCommandError(msg)
 
149
    
 
150
 
 
151
def get_merge_type(typestring):
 
152
    """Attempt to find the merge class/factory associated with a string."""
 
153
    from merge import merge_types
 
154
    try:
 
155
        return merge_types[typestring][0]
 
156
    except KeyError:
 
157
        templ = '%s%%7s: %%s' % (' '*12)
 
158
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
159
        type_list = '\n'.join(lines)
 
160
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
161
            (typestring, type_list)
 
162
        raise BzrCommandError(msg)
 
163
    
 
164
 
 
165
 
 
166
def _get_cmd_dict(plugins_override=True):
145
167
    import bzrlib.builtins
146
 
    r = {}
 
168
    
 
169
    d = {}
147
170
    builtins = bzrlib.builtins.__dict__
148
171
    for name in builtins:
149
172
        if name.startswith("cmd_"):
150
 
            real_name = _unsquish_command_name(name)        
151
 
            r[real_name] = builtins[name]
152
 
    return r
153
 
 
154
 
            
155
 
 
156
 
def builtin_command_names():
157
 
    """Return list of builtin command names."""
158
 
    return _builtin_commands().keys()
159
 
    
160
 
 
161
 
def plugin_command_names():
162
 
    return plugin_cmds.keys()
163
 
 
164
 
 
165
 
def _get_cmd_dict(plugins_override=True):
166
 
    """Return name->class mapping for all commands."""
167
 
    d = _builtin_commands()
 
173
            d[_unsquish_command_name(name)] = builtins[name]
 
174
    # If we didn't load plugins, the plugin_cmds dict will be empty
168
175
    if plugins_override:
169
176
        d.update(plugin_cmds)
 
177
    else:
 
178
        d2 = plugin_cmds.copy()
 
179
        d2.update(d)
 
180
        d = d2
170
181
    return d
171
182
 
172
183
    
176
187
        yield k,v
177
188
 
178
189
 
179
 
def get_cmd_object(cmd_name, plugins_override=True):
 
190
def get_cmd_class(cmd, plugins_override=True):
180
191
    """Return the canonical name and command class for a command.
181
 
 
182
 
    plugins_override
183
 
        If true, plugin commands can override builtins.
184
192
    """
185
 
    from bzrlib.externalcommand import ExternalCommand
186
 
 
187
 
    cmd_name = str(cmd_name)            # not unicode
 
193
    cmd = str(cmd)                      # not unicode
188
194
 
189
195
    # first look up this command under the specified name
190
196
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
197
    mutter("all commands: %r", cmds.keys())
191
198
    try:
192
 
        return cmds[cmd_name]()
 
199
        return cmd, cmds[cmd]
193
200
    except KeyError:
194
201
        pass
195
202
 
196
203
    # look for any command which claims this as an alias
197
 
    for real_cmd_name, cmd_class in cmds.iteritems():
198
 
        if cmd_name in cmd_class.aliases:
199
 
            return cmd_class()
200
 
 
201
 
    cmd_obj = ExternalCommand.find_command(cmd_name)
202
 
    if cmd_obj:
203
 
        return cmd_obj
204
 
 
205
 
    raise BzrCommandError("unknown command %r" % cmd_name)
 
204
    for cmdname, cmdclass in cmds.iteritems():
 
205
        if cmd in cmdclass.aliases:
 
206
            return cmdname, cmdclass
 
207
 
 
208
    cmdclass = ExternalCommand.find_command(cmd)
 
209
    if cmdclass:
 
210
        return cmd, cmdclass
 
211
 
 
212
    raise BzrCommandError("unknown command %r" % cmd)
206
213
 
207
214
 
208
215
class Command(object):
209
216
    """Base class for commands.
210
217
 
211
 
    Commands are the heart of the command-line bzr interface.
212
 
 
213
 
    The command object mostly handles the mapping of command-line
214
 
    parameters into one or more bzrlib operations, and of the results
215
 
    into textual output.
216
 
 
217
 
    Commands normally don't have any state.  All their arguments are
218
 
    passed in to the run method.  (Subclasses may take a different
219
 
    policy if the behaviour of the instance needs to depend on e.g. a
220
 
    shell plugin and not just its Python class.)
221
 
 
222
218
    The docstring for an actual command should give a single-line
223
219
    summary, then a complete description of the command.  A grammar
224
220
    description will be inserted.
225
221
 
226
 
    aliases
227
 
        Other accepted names for this command.
228
 
 
229
222
    takes_args
230
223
        List of argument forms, marked with whether they are optional,
231
224
        repeated, etc.
234
227
        List of options that may be given for this command.
235
228
 
236
229
    hidden
237
 
        If true, this command isn't advertised.  This is typically
238
 
        for commands intended for expert users.
 
230
        If true, this command isn't advertised.
239
231
    """
240
232
    aliases = []
241
233
    
244
236
 
245
237
    hidden = False
246
238
    
247
 
    def __init__(self):
248
 
        """Construct an instance of this command."""
 
239
    def __init__(self, options, arguments):
 
240
        """Construct and run the command.
 
241
 
 
242
        Sets self.status to the return value of run()."""
 
243
        assert isinstance(options, dict)
 
244
        assert isinstance(arguments, dict)
 
245
        cmdargs = options.copy()
 
246
        cmdargs.update(arguments)
249
247
        if self.__doc__ == Command.__doc__:
 
248
            from warnings import warn
250
249
            warn("No help message set for %r" % self)
251
 
 
252
 
 
253
 
    def run_argv(self, argv):
254
 
        """Parse command line and run."""
255
 
        args, opts = parse_args(argv)
256
 
 
257
 
        if 'help' in opts:  # e.g. bzr add --help
258
 
            from bzrlib.help import help_on_command
259
 
            help_on_command(self.name())
260
 
            return 0
261
 
 
262
 
        # check options are reasonable
263
 
        allowed = self.takes_options
264
 
        for oname in opts:
265
 
            if oname not in allowed:
266
 
                raise BzrCommandError("option '--%s' is not allowed for command %r"
267
 
                                      % (oname, self.name()))
268
 
 
269
 
        # mix arguments and options into one dictionary
270
 
        cmdargs = _match_argform(self.name(), self.takes_args, args)
271
 
        cmdopts = {}
272
 
        for k, v in opts.items():
273
 
            cmdopts[k.replace('-', '_')] = v
274
 
 
275
 
        all_cmd_args = cmdargs.copy()
276
 
        all_cmd_args.update(cmdopts)
277
 
 
278
 
        return self.run(**all_cmd_args)
 
250
        self.status = self.run(**cmdargs)
 
251
        if self.status is None:
 
252
            self.status = 0
279
253
 
280
254
    
281
 
    def run(self):
282
 
        """Actually run the command.
 
255
    def run(self, *args, **kwargs):
 
256
        """Override this in sub-classes.
283
257
 
284
258
        This is invoked with the options and arguments bound to
285
259
        keyword parameters.
286
260
 
287
 
        Return 0 or None if the command was successful, or a non-zero
288
 
        shell error code if not.  It's OK for this method to allow
289
 
        an exception to raise up.
 
261
        Return 0 or None if the command was successful, or a shell
 
262
        error code if not.
290
263
        """
291
264
        raise NotImplementedError()
292
265
 
293
266
 
294
 
    def help(self):
295
 
        """Return help message for this class."""
296
 
        if self.__doc__ is Command.__doc__:
297
 
            return None
298
 
        return getdoc(self)
299
 
 
300
 
    def name(self):
301
 
        return _unsquish_command_name(self.__class__.__name__)
 
267
class ExternalCommand(Command):
 
268
    """Class to wrap external commands.
 
269
 
 
270
    We cheat a little here, when get_cmd_class() calls us we actually
 
271
    give it back an object we construct that has the appropriate path,
 
272
    help, options etc for the specified command.
 
273
 
 
274
    When run_bzr() tries to instantiate that 'class' it gets caught by
 
275
    the __call__ method, which we override to call the Command.__init__
 
276
    method. That then calls our run method which is pretty straight
 
277
    forward.
 
278
 
 
279
    The only wrinkle is that we have to map bzr's dictionary of options
 
280
    and arguments back into command line options and arguments for the
 
281
    script.
 
282
    """
 
283
 
 
284
    def find_command(cls, cmd):
 
285
        import os.path
 
286
        bzrpath = os.environ.get('BZRPATH', '')
 
287
 
 
288
        for dir in bzrpath.split(os.pathsep):
 
289
            path = os.path.join(dir, cmd)
 
290
            if os.path.isfile(path):
 
291
                return ExternalCommand(path)
 
292
 
 
293
        return None
 
294
 
 
295
    find_command = classmethod(find_command)
 
296
 
 
297
    def __init__(self, path):
 
298
        self.path = path
 
299
 
 
300
        pipe = os.popen('%s --bzr-usage' % path, 'r')
 
301
        self.takes_options = pipe.readline().split()
 
302
 
 
303
        for opt in self.takes_options:
 
304
            if not opt in OPTIONS:
 
305
                raise BzrError("Unknown option '%s' returned by external command %s"
 
306
                               % (opt, path))
 
307
 
 
308
        # TODO: Is there any way to check takes_args is valid here?
 
309
        self.takes_args = pipe.readline().split()
 
310
 
 
311
        if pipe.close() is not None:
 
312
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
 
313
 
 
314
        pipe = os.popen('%s --bzr-help' % path, 'r')
 
315
        self.__doc__ = pipe.read()
 
316
        if pipe.close() is not None:
 
317
            raise BzrError("Failed funning '%s --bzr-help'" % path)
 
318
 
 
319
    def __call__(self, options, arguments):
 
320
        Command.__init__(self, options, arguments)
 
321
        return self
 
322
 
 
323
    def run(self, **kargs):
 
324
        opts = []
 
325
        args = []
 
326
 
 
327
        keys = kargs.keys()
 
328
        keys.sort()
 
329
        for name in keys:
 
330
            optname = name.replace('_','-')
 
331
            value = kargs[name]
 
332
            if OPTIONS.has_key(optname):
 
333
                # it's an option
 
334
                opts.append('--%s' % optname)
 
335
                if value is not None and value is not True:
 
336
                    opts.append(str(value))
 
337
            else:
 
338
                # it's an arg, or arg list
 
339
                if type(value) is not list:
 
340
                    value = [value]
 
341
                for v in value:
 
342
                    if v is not None:
 
343
                        args.append(str(v))
 
344
 
 
345
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
 
346
        return self.status
 
347
 
302
348
 
303
349
 
304
350
def parse_spec(spec):
333
379
    return parsed
334
380
 
335
381
 
 
382
 
 
383
 
336
384
# list of all available options; the rhs can be either None for an
337
385
# option that takes no argument, or a constructor function that checks
338
386
# the type.
339
387
OPTIONS = {
340
388
    'all':                    None,
341
 
    'basis':                  str,
342
389
    'diff-options':           str,
343
390
    'help':                   None,
344
391
    'file':                   unicode,
360
407
    'long':                   None,
361
408
    'root':                   str,
362
409
    'no-backup':              None,
 
410
    'merge-type':             get_merge_type,
363
411
    'pattern':                str,
364
412
    }
365
413
 
392
440
    >>> parse_args('commit --message=biter'.split())
393
441
    (['commit'], {'message': u'biter'})
394
442
    >>> parse_args('log -r 500'.split())
395
 
    (['log'], {'revision': [<RevisionSpec_int 500>]})
 
443
    (['log'], {'revision': [500]})
396
444
    >>> parse_args('log -r500..600'.split())
397
 
    (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
 
445
    (['log'], {'revision': [500, 600]})
398
446
    >>> parse_args('log -vr500..600'.split())
399
 
    (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
400
 
    >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
401
 
    (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
 
447
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
448
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
449
    (['log'], {'revision': ['v500', 600]})
402
450
    """
403
451
    args = []
404
452
    opts = {}
522
570
 
523
571
 
524
572
 
525
 
def apply_profiled(the_callable, *args, **kwargs):
526
 
    import hotshot
527
 
    import tempfile
528
 
    pffileno, pfname = tempfile.mkstemp()
529
 
    try:
530
 
        prof = hotshot.Profile(pfname)
531
 
        try:
532
 
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
533
 
        finally:
534
 
            prof.close()
535
 
 
536
 
        import hotshot.stats
537
 
        stats = hotshot.stats.load(pfname)
538
 
        #stats.strip_dirs()
539
 
        stats.sort_stats('time')
540
 
        ## XXX: Might like to write to stderr or the trace file instead but
541
 
        ## print_stats seems hardcoded to stdout
542
 
        stats.print_stats(20)
543
 
 
544
 
        return ret
545
 
    finally:
546
 
        os.close(pffileno)
547
 
        os.remove(pfname)
548
 
 
549
 
 
550
573
def run_bzr(argv):
551
574
    """Execute a command.
552
575
 
580
603
    # to load plugins before doing other command parsing so that they
581
604
    # can override commands, but this needs to happen first.
582
605
 
583
 
    for a in argv:
 
606
    for a in argv[:]:
584
607
        if a == '--profile':
585
608
            opt_profile = True
586
609
        elif a == '--no-plugins':
591
614
            break
592
615
        argv.remove(a)
593
616
 
594
 
    if (not argv) or (argv[0] == '--help'):
 
617
    if not opt_no_plugins:
 
618
        from bzrlib.plugin import load_plugins
 
619
        load_plugins()
 
620
 
 
621
    args, opts = parse_args(argv)
 
622
 
 
623
    if 'help' in opts:
595
624
        from bzrlib.help import help
596
 
        if len(argv) > 1:
597
 
            help(argv[1])
 
625
        if args:
 
626
            help(args[0])
598
627
        else:
599
628
            help()
600
 
        return 0
601
 
 
602
 
    if argv[0] == '--version':
 
629
        return 0            
 
630
        
 
631
    if 'version' in opts:
603
632
        from bzrlib.builtins import show_version
604
633
        show_version()
605
634
        return 0
606
 
        
607
 
    if not opt_no_plugins:
608
 
        from bzrlib.plugin import load_plugins
609
 
        load_plugins()
610
 
 
611
 
    cmd = str(argv.pop(0))
612
 
 
613
 
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
 
635
    
 
636
    if not args:
 
637
        from bzrlib.help import help
 
638
        help(None)
 
639
        return 0
 
640
    
 
641
    cmd = str(args.pop(0))
 
642
 
 
643
    canonical_cmd, cmd_class = \
 
644
                   get_cmd_class(cmd, plugins_override=not opt_builtin)
 
645
 
 
646
    # check options are reasonable
 
647
    allowed = cmd_class.takes_options
 
648
    for oname in opts:
 
649
        if oname not in allowed:
 
650
            raise BzrCommandError("option '--%s' is not allowed for command %r"
 
651
                                  % (oname, cmd))
 
652
 
 
653
    # mix arguments and options into one dictionary
 
654
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
655
    cmdopts = {}
 
656
    for k, v in opts.items():
 
657
        cmdopts[k.replace('-', '_')] = v
614
658
 
615
659
    if opt_profile:
616
 
        ret = apply_profiled(cmd_obj.run_argv, argv)
 
660
        import hotshot, tempfile
 
661
        pffileno, pfname = tempfile.mkstemp()
 
662
        try:
 
663
            prof = hotshot.Profile(pfname)
 
664
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
665
            prof.close()
 
666
 
 
667
            import hotshot.stats
 
668
            stats = hotshot.stats.load(pfname)
 
669
            #stats.strip_dirs()
 
670
            stats.sort_stats('time')
 
671
            ## XXX: Might like to write to stderr or the trace file instead but
 
672
            ## print_stats seems hardcoded to stdout
 
673
            stats.print_stats(20)
 
674
            
 
675
            return ret.status
 
676
 
 
677
        finally:
 
678
            os.close(pffileno)
 
679
            os.remove(pfname)
617
680
    else:
618
 
        ret = cmd_obj.run_argv(argv)
619
 
    return ret or 0
 
681
        return cmd_class(cmdopts, cmdargs).status 
620
682
 
621
683
 
622
684
def main(argv):
624
686
    bzrlib.trace.log_startup(argv)
625
687
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
626
688
 
627
 
    return run_bzr_catch_errors(argv[1:])
628
 
 
629
 
 
630
 
def run_bzr_catch_errors(argv):
631
689
    try:
632
690
        try:
633
 
            try:
634
 
                return run_bzr(argv)
635
 
            finally:
636
 
                # do this here inside the exception wrappers to catch EPIPE
637
 
                sys.stdout.flush()
638
 
        #wrap common errors as CommandErrors.
639
 
        except (NotBranchError,), e:
640
 
            raise BzrCommandError(str(e))
 
691
            return run_bzr(argv[1:])
 
692
        finally:
 
693
            # do this here inside the exception wrappers to catch EPIPE
 
694
            sys.stdout.flush()
641
695
    except BzrCommandError, e:
642
696
        # command line syntax error, etc
643
697
        log_error(str(e))
649
703
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
650
704
        return 3
651
705
    except KeyboardInterrupt, e:
652
 
        bzrlib.trace.log_exception('interrupted')
 
706
        bzrlib.trace.note('interrupted')
653
707
        return 2
654
708
    except Exception, e:
655
709
        import errno