1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
17
"""Bazaar-NG -- a free distributed version-control tool
 
 
20
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
 
 
22
* Metadata format is not stable yet -- you may need to
 
 
23
  discard history in the future.
 
 
25
* Many commands unimplemented or partially implemented.
 
 
27
* Space-inefficient storage.
 
 
29
* No merge operators yet.
 
 
36
      Show software version/licence/non-warranty.
 
 
38
      Start versioning the current directory
 
 
42
      Show revision history.
 
 
45
  bzr move FROM... DESTDIR
 
 
46
      Move one or more files to a different directory.
 
 
48
      Show changes from last revision to working copy.
 
 
49
  bzr commit -m 'MESSAGE'
 
 
50
      Store current state as new revision.
 
 
51
  bzr export [-r REVNO] DESTINATION
 
 
52
      Export the branch state at a previous version.
 
 
54
      Show summary of pending changes.
 
 
56
      Make a file not versioned.
 
 
58
      Show statistics about this branch.
 
 
60
      Verify history is stored safely. 
 
 
61
  (for more type 'bzr help commands')
 
 
67
import sys, os, time, os.path
 
 
71
from bzrlib.trace import mutter, note, log_error
 
 
72
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
 
73
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
 
74
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
 
75
from bzrlib.revision import Revision
 
 
76
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
 
90
def get_cmd_class(cmd):
 
 
93
    cmd = CMD_ALIASES.get(cmd, cmd)
 
 
96
        cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
 
 
98
        raise BzrError("unknown command %r" % cmd)
 
 
100
    return cmd, cmd_class
 
 
105
    """Base class for commands.
 
 
107
    The docstring for an actual command should give a single-line
 
 
108
    summary, then a complete description of the command.  A grammar
 
 
109
    description will be inserted.
 
 
112
        List of argument forms, marked with whether they are optional,
 
 
116
        List of options that may be given for this command.
 
 
119
        If true, this command isn't advertised.
 
 
128
    def __init__(self, options, arguments):
 
 
129
        """Construct and run the command.
 
 
131
        Sets self.status to the return value of run()."""
 
 
132
        assert isinstance(options, dict)
 
 
133
        assert isinstance(arguments, dict)
 
 
134
        cmdargs = options.copy()
 
 
135
        cmdargs.update(arguments)
 
 
136
        assert self.__doc__ != Command.__doc__, \
 
 
137
               ("No help message set for %r" % self)
 
 
138
        self.status = self.run(**cmdargs)
 
 
142
        """Override this in sub-classes.
 
 
144
        This is invoked with the options and arguments bound to
 
 
147
        Return 0 or None if the command was successful, or a shell
 
 
154
class cmd_status(Command):
 
 
155
    """Display status summary.
 
 
157
    For each file there is a single line giving its file state and name.
 
 
158
    The name is that in the current revision unless it is deleted or
 
 
159
    missing, in which case the old name is shown.
 
 
161
    takes_options = ['all']
 
 
163
    def run(self, all=False):
 
 
164
        #import bzrlib.status
 
 
165
        #bzrlib.status.tree_status(Branch('.'))
 
 
166
        Branch('.').show_status(show_all=all)
 
 
169
class cmd_cat_revision(Command):
 
 
170
    """Write out metadata for a revision."""
 
 
173
    takes_args = ['revision_id']
 
 
175
    def run(self, revision_id):
 
 
176
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
 
179
class cmd_revno(Command):
 
 
180
    """Show current revision number.
 
 
182
    This is equal to the number of revisions on this branch."""
 
 
184
        print Branch('.').revno()
 
 
187
class cmd_add(Command):
 
 
188
    """Add specified files or directories.
 
 
190
    In non-recursive mode, all the named items are added, regardless
 
 
191
    of whether they were previously ignored.  A warning is given if
 
 
192
    any of the named files are already versioned.
 
 
194
    In recursive mode (the default), files are treated the same way
 
 
195
    but the behaviour for directories is different.  Directories that
 
 
196
    are already versioned do not give a warning.  All directories,
 
 
197
    whether already versioned or not, are searched for files or
 
 
198
    subdirectories that are neither versioned or ignored, and these
 
 
199
    are added.  This search proceeds recursively into versioned
 
 
202
    Therefore simply saying 'bzr add .' will version all files that
 
 
203
    are currently unknown.
 
 
205
    TODO: Perhaps adding a file whose directly is not versioned should
 
 
206
    recursively add that parent, rather than giving an error?
 
 
208
    takes_args = ['file+']
 
 
209
    takes_options = ['verbose']
 
 
211
    def run(self, file_list, verbose=False):
 
 
212
        bzrlib.add.smart_add(file_list, verbose)
 
 
215
def Relpath(Command):
 
 
216
    """Show path of a file relative to root"""
 
 
217
    takes_args = ('filename')
 
 
220
        print Branch(self.args['filename']).relpath(filename)
 
 
224
class cmd_inventory(Command):
 
 
225
    """Show inventory of the current working copy or a revision."""
 
 
226
    takes_options = ['revision']
 
 
228
    def run(self, revision=None):
 
 
231
            inv = b.read_working_inventory()
 
 
233
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
 
235
        for path, entry in inv.iter_entries():
 
 
236
            print '%-50s %s' % (entry.file_id, path)
 
 
239
class cmd_move(Command):
 
 
240
    """Move files to a different directory.
 
 
245
    The destination must be a versioned directory in the same branch.
 
 
247
    takes_args = ['source$', 'dest']
 
 
248
    def run(self, source_list, dest):
 
 
251
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
 
254
class cmd_rename(Command):
 
 
255
    """Change the name of an entry.
 
 
258
      bzr rename frob.c frobber.c
 
 
259
      bzr rename src/frob.c lib/frob.c
 
 
261
    It is an error if the destination name exists.
 
 
263
    See also the 'move' command, which moves files into a different
 
 
264
    directory without changing their name.
 
 
266
    TODO: Some way to rename multiple files without invoking bzr for each
 
 
268
    takes_args = ['from_name', 'to_name']
 
 
270
    def run(self, from_name, to_name):
 
 
272
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
 
276
class cmd_renames(Command):
 
 
277
    """Show list of renamed files.
 
 
279
    TODO: Option to show renames between two historical versions.
 
 
281
    TODO: Only show renames under dir, rather than in the whole branch.
 
 
283
    takes_args = ['dir?']
 
 
285
    def run(self, dir='.'):
 
 
287
        old_inv = b.basis_tree().inventory
 
 
288
        new_inv = b.read_working_inventory()
 
 
290
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
 
292
        for old_name, new_name in renames:
 
 
293
            print "%s => %s" % (old_name, new_name)        
 
 
296
class cmd_info(Command):
 
 
297
    """Show statistical information for this branch"""
 
 
300
        info.show_info(Branch('.'))        
 
 
303
class cmd_remove(Command):
 
 
304
    """Make a file unversioned.
 
 
306
    This makes bzr stop tracking changes to a versioned file.  It does
 
 
307
    not delete the working copy.
 
 
309
    takes_args = ['file+']
 
 
310
    takes_options = ['verbose']
 
 
312
    def run(self, file_list, verbose=False):
 
 
313
        b = Branch(file_list[0])
 
 
314
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
 
317
class cmd_file_id(Command):
 
 
318
    """Print file_id of a particular file or directory.
 
 
320
    The file_id is assigned when the file is first added and remains the
 
 
321
    same through all revisions where the file exists, even when it is
 
 
325
    takes_args = ['filename']
 
 
326
    def run(self, filename):
 
 
328
        i = b.inventory.path2id(b.relpath(filename))
 
 
330
            bailout("%r is not a versioned file" % filename)
 
 
335
class cmd_file_path(Command):
 
 
336
    """Print path of file_ids to a file or directory.
 
 
338
    This prints one line for each directory down to the target,
 
 
339
    starting at the branch root."""
 
 
341
    takes_args = ['filename']
 
 
342
    def run(self, filename):
 
 
345
        fid = inv.path2id(b.relpath(filename))
 
 
347
            bailout("%r is not a versioned file" % filename)
 
 
348
        for fip in inv.get_idpath(fid):
 
 
352
class cmd_revision_history(Command):
 
 
353
    """Display list of revision ids on this branch."""
 
 
355
        for patchid in Branch('.').revision_history():
 
 
359
class cmd_directories(Command):
 
 
360
    """Display list of versioned directories in this branch."""
 
 
362
        for name, ie in Branch('.').read_working_inventory().directories():
 
 
369
class cmd_init(Command):
 
 
370
    """Make a directory into a versioned branch.
 
 
372
    Use this to create an empty branch, or before importing an
 
 
375
    Recipe for importing a tree of files:
 
 
380
        bzr commit -m 'imported project'
 
 
383
        Branch('.', init=True)
 
 
386
class cmd_diff(Command):
 
 
387
    """Show differences in working tree.
 
 
389
    If files are listed, only the changes in those files are listed.
 
 
390
    Otherwise, all changes for the tree are listed.
 
 
392
    TODO: Given two revision arguments, show the difference between them.
 
 
394
    TODO: Allow diff across branches.
 
 
396
    TODO: Option to use external diff command; could be GNU diff, wdiff,
 
 
399
    TODO: Python difflib is not exactly the same as unidiff; should
 
 
400
          either fix it up or prefer to use an external diff.
 
 
402
    TODO: If a directory is given, diff everything under that.
 
 
404
    TODO: Selected-file diff is inefficient and doesn't show you
 
 
407
    TODO: This probably handles non-Unix newlines poorly.
 
 
410
    takes_args = ['file*']
 
 
411
    takes_options = ['revision']
 
 
413
    def run(self, revision=None, file_list=None):
 
 
414
        from bzrlib.diff import show_diff
 
 
416
        show_diff(Branch('.'), revision, file_list)
 
 
419
class cmd_deleted(Command):
 
 
420
    """List files deleted in the working tree.
 
 
422
    TODO: Show files deleted since a previous revision, or between two revisions.
 
 
424
    def run(self, show_ids=False):
 
 
427
        new = b.working_tree()
 
 
429
        ## TODO: Much more efficient way to do this: read in new
 
 
430
        ## directories with readdir, rather than stating each one.  Same
 
 
431
        ## level of effort but possibly much less IO.  (Or possibly not,
 
 
432
        ## if the directories are very large...)
 
 
434
        for path, ie in old.inventory.iter_entries():
 
 
435
            if not new.has_id(ie.file_id):
 
 
437
                    print '%-50s %s' % (path, ie.file_id)
 
 
441
class cmd_root(Command):
 
 
442
    """Show the tree root directory.
 
 
444
    The root is the nearest enclosing directory with a .bzr control
 
 
446
    takes_args = ['filename?']
 
 
447
    def run(self, filename=None):
 
 
448
        """Print the branch root."""
 
 
449
        print bzrlib.branch.find_branch_root(filename)
 
 
453
class cmd_log(Command):
 
 
454
    """Show log of this branch.
 
 
456
    TODO: Options to show ids; to limit range; etc.
 
 
458
    takes_options = ['timezone', 'verbose']
 
 
459
    def run(self, timezone='original', verbose=False):
 
 
460
        Branch('.').write_log(show_timezone=timezone, verbose=verbose)
 
 
463
class cmd_ls(Command):
 
 
464
    """List files in a tree.
 
 
466
    TODO: Take a revision or remote path and list that tree instead.
 
 
469
    def run(self, revision=None, verbose=False):
 
 
472
            tree = b.working_tree()
 
 
474
            tree = b.revision_tree(b.lookup_revision(revision))
 
 
476
        for fp, fc, kind, fid in tree.list_files():
 
 
478
                if kind == 'directory':
 
 
485
                print '%-8s %s%s' % (fc, fp, kindch)
 
 
491
class cmd_unknowns(Command):
 
 
492
    """List unknown files"""
 
 
494
        for f in Branch('.').unknowns():
 
 
499
class cmd_ignore(Command):
 
 
500
    """Ignore a command or pattern"""
 
 
501
    takes_args = ['name_pattern']
 
 
503
    def run(self, name_pattern):
 
 
506
        # XXX: This will fail if it's a hardlink; should use an AtomicFile class.
 
 
507
        f = open(b.abspath('.bzrignore'), 'at')
 
 
508
        f.write(name_pattern + '\n')
 
 
511
        inv = b.working_tree().inventory
 
 
512
        if inv.path2id('.bzrignore'):
 
 
513
            mutter('.bzrignore is already versioned')
 
 
515
            mutter('need to make new .bzrignore file versioned')
 
 
516
            b.add(['.bzrignore'])
 
 
520
class cmd_ignored(Command):
 
 
521
    """List ignored files and the patterns that matched them."""
 
 
523
        tree = Branch('.').working_tree()
 
 
524
        for path, file_class, kind, file_id in tree.list_files():
 
 
525
            if file_class != 'I':
 
 
527
            ## XXX: Slightly inefficient since this was already calculated
 
 
528
            pat = tree.is_ignored(path)
 
 
529
            print '%-50s %s' % (path, pat)
 
 
532
class cmd_lookup_revision(Command):
 
 
533
    """Lookup the revision-id from a revision-number
 
 
536
        bzr lookup-revision 33
 
 
539
    takes_args = ['revno']
 
 
541
    def run(self, revno):
 
 
545
            raise BzrCommandError("not a valid revision-number: %r" % revno)
 
 
547
        print Branch('.').lookup_revision(revno)
 
 
550
class cmd_export(Command):
 
 
551
    """Export past revision to destination directory.
 
 
553
    If no revision is specified this exports the last committed revision."""
 
 
554
    takes_args = ['dest']
 
 
555
    takes_options = ['revision']
 
 
556
    def run(self, dest, revno=None):
 
 
559
            rh = b.revision_history[-1]
 
 
561
            rh = b.lookup_revision(int(revno))
 
 
562
        t = b.revision_tree(rh)
 
 
566
class cmd_cat(Command):
 
 
567
    """Write a file's text from a previous revision."""
 
 
569
    takes_options = ['revision']
 
 
570
    takes_args = ['filename']
 
 
572
    def run(self, filename, revision=None):
 
 
574
            raise BzrCommandError("bzr cat requires a revision number")
 
 
576
        b.print_file(b.relpath(filename), int(revision))
 
 
579
class cmd_local_time_offset(Command):
 
 
580
    """Show the offset in seconds from GMT to local time."""
 
 
583
        print bzrlib.osutils.local_time_offset()
 
 
587
class cmd_commit(Command):
 
 
588
    """Commit changes into a new revision.
 
 
590
    TODO: Commit only selected files.
 
 
592
    TODO: Run hooks on tree to-be-committed, and after commit.
 
 
594
    TODO: Strict commit that fails if there are unknown or deleted files.
 
 
596
    takes_options = ['message', 'verbose']
 
 
598
    def run(self, message=None, verbose=False):
 
 
600
            raise BzrCommandError("please specify a commit message")
 
 
601
        Branch('.').commit(message, verbose=verbose)
 
 
604
class cmd_check(Command):
 
 
605
    """Validate consistency of branch history.
 
 
607
    This command checks various invariants about the branch storage to
 
 
608
    detect data corruption or bzr bugs.
 
 
610
    takes_args = ['dir?']
 
 
611
    def run(self, dir='.'):
 
 
613
        bzrlib.check.check(Branch(dir, find_root=False))
 
 
617
class cmd_whoami(Command):
 
 
618
    """Show bzr user id."""
 
 
619
    takes_options = ['email']
 
 
621
    def run(self, email=False):
 
 
623
            print bzrlib.osutils.user_email()
 
 
625
            print bzrlib.osutils.username()
 
 
628
class cmd_selftest(Command):
 
 
629
    """Run internal test suite"""
 
 
632
        failures, tests = 0, 0
 
 
634
        import doctest, bzrlib.store, bzrlib.tests
 
 
635
        bzrlib.trace.verbose = False
 
 
637
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
 
638
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
 
639
            mf, mt = doctest.testmod(m)
 
 
642
            print '%-40s %3d tests' % (m.__name__, mt),
 
 
644
                print '%3d FAILED!' % mf
 
 
648
        print '%-40s %3d tests' % ('total', tests),
 
 
650
            print '%3d FAILED!' % failures
 
 
656
class cmd_version(Command):
 
 
657
    """Show version of bzr"""
 
 
662
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
 
663
    print bzrlib.__copyright__
 
 
664
    print "http://bazaar-ng.org/"
 
 
666
    print "bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and"
 
 
667
    print "you may use, modify and redistribute it under the terms of the GNU"
 
 
668
    print "General Public License version 2 or later."
 
 
671
class cmd_rocks(Command):
 
 
672
    """Statement of optimism."""
 
 
675
        print "it sure does!"
 
 
678
class cmd_assert_fail(Command):
 
 
679
    """Test reporting of assertion failures"""
 
 
682
        assert False, "always fails"
 
 
685
class cmd_help(Command):
 
 
686
    """Show help on a command or other topic.
 
 
688
    For a list of all available commands, say 'bzr help commands'."""
 
 
689
    takes_args = ['topic?']
 
 
691
    def run(self, topic=None):
 
 
695
def help(topic=None):
 
 
698
    elif topic == 'commands':
 
 
701
        help_on_command(topic)
 
 
704
def help_on_command(cmdname):
 
 
705
    cmdname = str(cmdname)
 
 
707
    from inspect import getdoc
 
 
708
    topic, cmdclass = get_cmd_class(cmdname)
 
 
710
    doc = getdoc(cmdclass)
 
 
712
        raise NotImplementedError("sorry, no detailed help yet for %r" % cmdname)
 
 
715
        short, rest = doc.split('\n', 1)
 
 
720
    print 'usage: bzr ' + topic,
 
 
721
    for aname in cmdclass.takes_args:
 
 
722
        aname = aname.upper()
 
 
723
        if aname[-1] in ['$', '+']:
 
 
724
            aname = aname[:-1] + '...'
 
 
725
        elif aname[-1] == '?':
 
 
726
            aname = '[' + aname[:-1] + ']'
 
 
727
        elif aname[-1] == '*':
 
 
728
            aname = '[' + aname[:-1] + '...]'
 
 
735
    help_on_option(cmdclass.takes_options)
 
 
738
def help_on_option(options):
 
 
746
        for shortname, longname in SHORT_OPTIONS.items():
 
 
748
                l += ', -' + shortname
 
 
754
    """List all commands"""
 
 
758
    for k, v in globals().items():
 
 
759
        if k.startswith('cmd_'):
 
 
760
            accu.append((k[4:].replace('_','-'), v))
 
 
762
    for cmdname, cmdclass in accu:
 
 
766
        help = inspect.getdoc(cmdclass)
 
 
768
            print "    " + help.split('\n', 1)[0]
 
 
771
######################################################################
 
 
775
# list of all available options; the rhs can be either None for an
 
 
776
# option that takes no argument, or a constructor function that checks
 
 
798
def parse_args(argv):
 
 
799
    """Parse command line.
 
 
801
    Arguments and options are parsed at this level before being passed
 
 
802
    down to specific command handlers.  This routine knows, from a
 
 
803
    lookup table, something about the available options, what optargs
 
 
804
    they take, and which commands will accept them.
 
 
806
    >>> parse_args('--help'.split())
 
 
808
    >>> parse_args('--version'.split())
 
 
809
    ([], {'version': True})
 
 
810
    >>> parse_args('status --all'.split())
 
 
811
    (['status'], {'all': True})
 
 
812
    >>> parse_args('commit --message=biter'.split())
 
 
813
    (['commit'], {'message': u'biter'})
 
 
818
    # TODO: Maybe handle '--' to end options?
 
 
823
            # option names must not be unicode
 
 
827
                mutter("  got option %r" % a)
 
 
829
                    optname, optarg = a[2:].split('=', 1)
 
 
832
                if optname not in OPTIONS:
 
 
833
                    bailout('unknown long option %r' % a)
 
 
836
                if shortopt not in SHORT_OPTIONS:
 
 
837
                    bailout('unknown short option %r' % a)
 
 
838
                optname = SHORT_OPTIONS[shortopt]
 
 
841
                # XXX: Do we ever want to support this, e.g. for -r?
 
 
842
                bailout('repeated option %r' % a)
 
 
844
            optargfn = OPTIONS[optname]
 
 
848
                        bailout('option %r needs an argument' % a)
 
 
851
                opts[optname] = optargfn(optarg)
 
 
854
                    bailout('option %r takes no argument' % optname)
 
 
864
def _match_argform(cmd, takes_args, args):
 
 
867
    # step through args and takes_args, allowing appropriate 0-many matches
 
 
868
    for ap in takes_args:
 
 
872
                argdict[argname] = args.pop(0)
 
 
873
        elif ap[-1] == '*': # all remaining arguments
 
 
875
                argdict[argname + '_list'] = args[:]
 
 
878
                argdict[argname + '_list'] = None
 
 
881
                raise BzrCommandError("command %r needs one or more %s"
 
 
882
                        % (cmd, argname.upper()))
 
 
884
                argdict[argname + '_list'] = args[:]
 
 
886
        elif ap[-1] == '$': # all but one
 
 
888
                raise BzrCommandError("command %r needs one or more %s"
 
 
889
                        % (cmd, argname.upper()))
 
 
890
            argdict[argname + '_list'] = args[:-1]
 
 
896
                raise BzrCommandError("command %r requires argument %s"
 
 
897
                        % (cmd, argname.upper()))
 
 
899
                argdict[argname] = args.pop(0)
 
 
902
        raise BzrCommandError("extra argument to command %s: %s"
 
 
910
    """Execute a command.
 
 
912
    This is similar to main(), but without all the trappings for
 
 
913
    logging and error handling.  
 
 
916
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
 
919
        args, opts = parse_args(argv[1:])
 
 
926
        elif 'version' in opts:
 
 
929
        cmd = str(args.pop(0))
 
 
931
        log_error('usage: bzr COMMAND')
 
 
932
        log_error('  try "bzr help"')
 
 
935
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
 
938
    if 'profile' in opts:
 
 
944
    # check options are reasonable
 
 
945
    allowed = cmd_class.takes_options
 
 
947
        if oname not in allowed:
 
 
948
            raise BzrCommandError("option %r is not allowed for command %r"
 
 
951
    # mix arguments and options into one dictionary
 
 
952
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
 
954
    for k, v in opts.items():
 
 
955
        cmdopts[k.replace('-', '_')] = v
 
 
958
        import hotshot, tempfile
 
 
959
        pffileno, pfname = tempfile.mkstemp()
 
 
961
            prof = hotshot.Profile(pfname)
 
 
962
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
 
966
            stats = hotshot.stats.load(pfname)
 
 
968
            stats.sort_stats('time')
 
 
969
            ## XXX: Might like to write to stderr or the trace file instead but
 
 
970
            ## print_stats seems hardcoded to stdout
 
 
971
            stats.print_stats(20)
 
 
979
        cmdobj = cmd_class(cmdopts, cmdargs).status 
 
 
982
def _report_exception(e, summary, quiet=False):
 
 
984
    log_error('bzr: ' + summary)
 
 
985
    bzrlib.trace.log_exception(e)
 
 
988
        tb = sys.exc_info()[2]
 
 
989
        exinfo = traceback.extract_tb(tb)
 
 
991
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
 
 
992
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
 
 
999
    bzrlib.trace.create_tracefile(argv)
 
 
1004
                return run_bzr(argv)
 
 
1006
                # do this here inside the exception wrappers to catch EPIPE
 
 
1009
            quiet = isinstance(e, (BzrCommandError))
 
 
1010
            _report_exception(e, 'error: ' + e.args[0], quiet=quiet)
 
 
1013
                    # some explanation or hints
 
 
1016
        except AssertionError, e:
 
 
1017
            msg = 'assertion failed'
 
 
1019
                msg += ': ' + str(e)
 
 
1020
            _report_exception(e, msg)
 
 
1022
        except KeyboardInterrupt, e:
 
 
1023
            _report_exception(e, 'interrupted', quiet=True)
 
 
1025
        except Exception, e:
 
 
1027
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
 
 
1031
                msg = str(e).rstrip('\n')
 
 
1032
            _report_exception(e, msg, quiet)
 
 
1035
        bzrlib.trace.close_trace()
 
 
1038
if __name__ == '__main__':
 
 
1039
    sys.exit(main(sys.argv))