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, types, shutil, tempfile, fnmatch, difflib, os.path
 
 
69
from pprint import pprint
 
 
74
from bzrlib.store import ImmutableStore
 
 
75
from bzrlib.trace import mutter, note, log_error
 
 
76
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
 
77
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
 
78
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
 
79
from bzrlib.revision import Revision
 
 
80
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
 
83
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
 
 
84
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
 
 
86
## standard representation
 
 
87
NONE_STRING = '(none)'
 
 
101
def get_cmd_class(cmd):
 
 
104
    cmd = CMD_ALIASES.get(cmd, cmd)
 
 
107
        cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
 
 
109
        raise BzrError("unknown command %r" % cmd)
 
 
111
    return cmd, cmd_class
 
 
116
    """Base class for commands.
 
 
118
    The docstring for an actual command should give a single-line
 
 
119
    summary, then a complete description of the command.  A grammar
 
 
120
    description will be inserted.
 
 
123
        List of argument forms, marked with whether they are optional,
 
 
127
        List of options that may be given for this command.
 
 
130
        If true, this command isn't advertised.
 
 
139
    def __init__(self, options, arguments):
 
 
140
        """Construct and run the command.
 
 
142
        Sets self.status to the return value of run()."""
 
 
143
        assert isinstance(options, dict)
 
 
144
        assert isinstance(arguments, dict)
 
 
145
        cmdargs = options.copy()
 
 
146
        cmdargs.update(arguments)
 
 
147
        assert self.__doc__ != Command.__doc__, \
 
 
148
               ("No help message set for %r" % self)
 
 
149
        self.status = self.run(**cmdargs)
 
 
153
        """Override this in sub-classes.
 
 
155
        This is invoked with the options and arguments bound to
 
 
158
        Return True if the command was successful, False if not.
 
 
164
class cmd_status(Command):
 
 
165
    """Display status summary.
 
 
167
    For each file there is a single line giving its file state and name.
 
 
168
    The name is that in the current revision unless it is deleted or
 
 
169
    missing, in which case the old name is shown.
 
 
171
    takes_options = ['all']
 
 
173
    def run(self, all=False):
 
 
174
        #import bzrlib.status
 
 
175
        #bzrlib.status.tree_status(Branch('.'))
 
 
176
        Branch('.').show_status(show_all=all)
 
 
179
class cmd_cat_revision(Command):
 
 
180
    """Write out metadata for a revision."""
 
 
183
    takes_args = ['revision_id']
 
 
185
    def run(self, revision_id):
 
 
186
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
 
189
class cmd_revno(Command):
 
 
190
    """Show current revision number.
 
 
192
    This is equal to the number of revisions on this branch."""
 
 
194
        print Branch('.').revno()
 
 
197
class cmd_add(Command):
 
 
198
    """Add specified files or directories.
 
 
200
    In non-recursive mode, all the named items are added, regardless
 
 
201
    of whether they were previously ignored.  A warning is given if
 
 
202
    any of the named files are already versioned.
 
 
204
    In recursive mode (the default), files are treated the same way
 
 
205
    but the behaviour for directories is different.  Directories that
 
 
206
    are already versioned do not give a warning.  All directories,
 
 
207
    whether already versioned or not, are searched for files or
 
 
208
    subdirectories that are neither versioned or ignored, and these
 
 
209
    are added.  This search proceeds recursively into versioned
 
 
212
    Therefore simply saying 'bzr add .' will version all files that
 
 
213
    are currently unknown.
 
 
215
    TODO: Perhaps adding a file whose directly is not versioned should
 
 
216
    recursively add that parent, rather than giving an error?
 
 
218
    takes_args = ['file+']
 
 
219
    takes_options = ['verbose']
 
 
221
    def run(self, file_list, verbose=False):
 
 
222
        bzrlib.add.smart_add(file_list, verbose)
 
 
225
def Relpath(Command):
 
 
226
    """Show path of a file relative to root"""
 
 
227
    takes_args = ('filename')
 
 
230
        print Branch(self.args['filename']).relpath(filename)
 
 
234
class cmd_inventory(Command):
 
 
235
    """Show inventory of the current working copy or a revision."""
 
 
236
    takes_options = ['revision']
 
 
238
    def run(self, revision=None):
 
 
241
            inv = b.read_working_inventory()
 
 
243
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
 
245
        for path, entry in inv.iter_entries():
 
 
246
            print '%-50s %s' % (entry.file_id, path)
 
 
249
class cmd_move(Command):
 
 
250
    """Move files to a different directory.
 
 
255
    The destination must be a versioned directory in the same branch.
 
 
257
    takes_args = ['source$', 'dest']
 
 
258
    def run(self, source_list, dest):
 
 
261
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
 
264
class cmd_rename(Command):
 
 
265
    """Change the name of an entry.
 
 
268
      bzr rename frob.c frobber.c
 
 
269
      bzr rename src/frob.c lib/frob.c
 
 
271
    It is an error if the destination name exists.
 
 
273
    See also the 'move' command, which moves files into a different
 
 
274
    directory without changing their name.
 
 
276
    TODO: Some way to rename multiple files without invoking bzr for each
 
 
278
    takes_args = ['from_name', 'to_name']
 
 
280
    def run(self, from_name, to_name):
 
 
282
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
 
286
class cmd_renames(Command):
 
 
287
    """Show list of renamed files.
 
 
289
    TODO: Option to show renames between two historical versions.
 
 
291
    TODO: Only show renames under dir, rather than in the whole branch.
 
 
293
    takes_args = ['dir?']
 
 
295
    def run(self, dir='.'):
 
 
297
        old_inv = b.basis_tree().inventory
 
 
298
        new_inv = b.read_working_inventory()
 
 
300
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
 
302
        for old_name, new_name in renames:
 
 
303
            print "%s => %s" % (old_name, new_name)        
 
 
306
class cmd_info(Command):
 
 
307
    """Show statistical information for this branch"""
 
 
310
        info.show_info(Branch('.'))        
 
 
313
class cmd_remove(Command):
 
 
314
    """Make a file unversioned.
 
 
316
    This makes bzr stop tracking changes to a versioned file.  It does
 
 
317
    not delete the working copy.
 
 
319
    takes_args = ['file+']
 
 
320
    takes_options = ['verbose']
 
 
322
    def run(self, file_list, verbose=False):
 
 
323
        b = Branch(file_list[0])
 
 
324
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
 
327
class cmd_file_id(Command):
 
 
328
    """Print file_id of a particular file or directory.
 
 
330
    The file_id is assigned when the file is first added and remains the
 
 
331
    same through all revisions where the file exists, even when it is
 
 
335
    takes_args = ['filename']
 
 
336
    def run(self, filename):
 
 
338
        i = b.inventory.path2id(b.relpath(filename))
 
 
340
            bailout("%r is not a versioned file" % filename)
 
 
345
class cmd_file_path(Command):
 
 
346
    """Print path of file_ids to a file or directory.
 
 
348
    This prints one line for each directory down to the target,
 
 
349
    starting at the branch root."""
 
 
351
    takes_args = ['filename']
 
 
352
    def run(self, filename):
 
 
355
        fid = inv.path2id(b.relpath(filename))
 
 
357
            bailout("%r is not a versioned file" % filename)
 
 
358
        for fip in inv.get_idpath(fid):
 
 
362
class cmd_revision_history(Command):
 
 
363
    """Display list of revision ids on this branch."""
 
 
365
        for patchid in Branch('.').revision_history():
 
 
369
class cmd_directories(Command):
 
 
370
    """Display list of versioned directories in this branch."""
 
 
372
        for name, ie in Branch('.').read_working_inventory().directories():
 
 
379
class cmd_init(Command):
 
 
380
    """Make a directory into a versioned branch.
 
 
382
    Use this to create an empty branch, or before importing an
 
 
385
    Recipe for importing a tree of files:
 
 
390
        bzr commit -m 'imported project'
 
 
393
        Branch('.', init=True)
 
 
396
class cmd_diff(Command):
 
 
397
    """Show differences in working tree.
 
 
399
    If files are listed, only the changes in those files are listed.
 
 
400
    Otherwise, all changes for the tree are listed.
 
 
402
    TODO: Given two revision arguments, show the difference between them.
 
 
404
    TODO: Allow diff across branches.
 
 
406
    TODO: Option to use external diff command; could be GNU diff, wdiff,
 
 
409
    TODO: Python difflib is not exactly the same as unidiff; should
 
 
410
          either fix it up or prefer to use an external diff.
 
 
412
    TODO: If a directory is given, diff everything under that.
 
 
414
    TODO: Selected-file diff is inefficient and doesn't show you
 
 
417
    TODO: This probably handles non-Unix newlines poorly.
 
 
420
    takes_args = ['file*']
 
 
421
    takes_options = ['revision']
 
 
423
    def run(self, revision=None, file_list=None):
 
 
424
        from bzrlib.diff import show_diff
 
 
426
        show_diff(Branch('.'), revision, file_list)
 
 
429
class cmd_deleted(Command):
 
 
430
    """List files deleted in the working tree.
 
 
432
    TODO: Show files deleted since a previous revision, or between two revisions.
 
 
434
    def run(self, show_ids=False):
 
 
437
        new = b.working_tree()
 
 
439
        ## TODO: Much more efficient way to do this: read in new
 
 
440
        ## directories with readdir, rather than stating each one.  Same
 
 
441
        ## level of effort but possibly much less IO.  (Or possibly not,
 
 
442
        ## if the directories are very large...)
 
 
444
        for path, ie in old.inventory.iter_entries():
 
 
445
            if not new.has_id(ie.file_id):
 
 
447
                    print '%-50s %s' % (path, ie.file_id)
 
 
451
class cmd_root(Command):
 
 
452
    """Show the tree root directory.
 
 
454
    The root is the nearest enclosing directory with a .bzr control
 
 
456
    takes_args = ['filename?']
 
 
457
    def run(self, filename=None):
 
 
458
        """Print the branch root."""
 
 
459
        print bzrlib.branch.find_branch_root(filename)
 
 
463
class cmd_log(Command):
 
 
464
    """Show log of this branch.
 
 
466
    TODO: Options to show ids; to limit range; etc.
 
 
468
    takes_options = ['timezone', 'verbose']
 
 
469
    def run(self, timezone='original', verbose=False):
 
 
470
        Branch('.').write_log(show_timezone=timezone, verbose=verbose)
 
 
473
class cmd_ls(Command):
 
 
474
    """List files in a tree.
 
 
476
    TODO: Take a revision or remote path and list that tree instead.
 
 
479
    def run(self, revision=None, verbose=False):
 
 
482
            tree = b.working_tree()
 
 
484
            tree = b.revision_tree(b.lookup_revision(revision))
 
 
486
        for fp, fc, kind, fid in tree.list_files():
 
 
488
                if kind == 'directory':
 
 
495
                print '%-8s %s%s' % (fc, fp, kindch)
 
 
501
class cmd_unknowns(Command):
 
 
502
    """List unknown files"""
 
 
504
        for f in Branch('.').unknowns():
 
 
509
class cmd_ignore(Command):
 
 
510
    """Ignore a command or pattern"""
 
 
511
    takes_args = ['name_pattern']
 
 
513
    def run(self, name_pattern):
 
 
516
        # XXX: This will fail if it's a hardlink; should use an AtomicFile class.
 
 
517
        f = open(b.abspath('.bzrignore'), 'at')
 
 
518
        f.write(name_pattern + '\n')
 
 
521
        inv = b.working_tree().inventory
 
 
522
        if inv.path2id('.bzrignore'):
 
 
523
            mutter('.bzrignore is already versioned')
 
 
525
            mutter('need to make new .bzrignore file versioned')
 
 
526
            b.add(['.bzrignore'])
 
 
530
class cmd_ignored(Command):
 
 
531
    """List ignored files and the patterns that matched them."""
 
 
533
        tree = Branch('.').working_tree()
 
 
534
        for path, file_class, kind, file_id in tree.list_files():
 
 
535
            if file_class != 'I':
 
 
537
            ## XXX: Slightly inefficient since this was already calculated
 
 
538
            pat = tree.is_ignored(path)
 
 
539
            print '%-50s %s' % (path, pat)
 
 
542
class cmd_lookup_revision(Command):
 
 
543
    """Lookup the revision-id from a revision-number
 
 
546
        bzr lookup-revision 33
 
 
549
    def run(self, revno):
 
 
553
            raise BzrError("not a valid revision-number: %r" % revno)
 
 
555
        print Branch('.').lookup_revision(revno) or NONE_STRING
 
 
559
class cmd_export(Command):
 
 
560
    """Export past revision to destination directory.
 
 
562
    If no revision is specified this exports the last committed revision."""
 
 
563
    takes_args = ['dest']
 
 
564
    takes_options = ['revision']
 
 
565
    def run(self, dest, revno=None):
 
 
568
            rh = b.revision_history[-1]
 
 
570
            rh = b.lookup_revision(int(revno))
 
 
571
        t = b.revision_tree(rh)
 
 
575
class cmd_cat(Command):
 
 
576
    """Write a file's text from a previous revision."""
 
 
578
    takes_options = ['revision']
 
 
579
    takes_args = ['filename']
 
 
581
    def run(self, filename, revision=None):
 
 
583
            raise BzrCommandError("bzr cat requires a revision number")
 
 
585
        b.print_file(b.relpath(filename), int(revision))
 
 
588
class cmd_local_time_offset(Command):
 
 
589
    """Show the offset in seconds from GMT to local time."""
 
 
592
        print bzrlib.osutils.local_time_offset()
 
 
596
class cmd_commit(Command):
 
 
597
    """Commit changes into a new revision.
 
 
599
    TODO: Commit only selected files.
 
 
601
    TODO: Run hooks on tree to-be-committed, and after commit.
 
 
603
    TODO: Strict commit that fails if there are unknown or deleted files.
 
 
605
    takes_options = ['message', 'verbose']
 
 
607
    def run(self, message=None, verbose=False):
 
 
609
            raise BzrCommandError("please specify a commit message")
 
 
610
        Branch('.').commit(message, verbose=verbose)
 
 
613
class cmd_check(Command):
 
 
614
    """Validate consistency of branch history.
 
 
616
    This command checks various invariants about the branch storage to
 
 
617
    detect data corruption or bzr bugs.
 
 
619
    takes_args = ['dir?']
 
 
620
    def run(self, dir='.'):
 
 
622
        bzrlib.check.check(Branch(dir, find_root=False))
 
 
626
class cmd_whoami(Command):
 
 
627
    """Show bzr user id."""
 
 
628
    takes_options = ['email']
 
 
630
    def run(self, email=False):
 
 
632
            print bzrlib.osutils.user_email()
 
 
634
            print bzrlib.osutils.username()
 
 
637
class cmd_selftest(Command):
 
 
638
    """Run internal test suite"""
 
 
641
        failures, tests = 0, 0
 
 
643
        import doctest, bzrlib.store, bzrlib.tests
 
 
644
        bzrlib.trace.verbose = False
 
 
646
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
 
647
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
 
648
            mf, mt = doctest.testmod(m)
 
 
651
            print '%-40s %3d tests' % (m.__name__, mt),
 
 
653
                print '%3d FAILED!' % mf
 
 
657
        print '%-40s %3d tests' % ('total', tests),
 
 
659
            print '%3d FAILED!' % failures
 
 
665
class cmd_version(Command):
 
 
666
    """Show version of bzr"""
 
 
671
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
 
672
    print bzrlib.__copyright__
 
 
673
    print "http://bazaar-ng.org/"
 
 
675
    print "bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and"
 
 
676
    print "you may use, modify and redistribute it under the terms of the GNU"
 
 
677
    print "General Public License version 2 or later."
 
 
680
class cmd_rocks(Command):
 
 
681
    """Statement of optimism."""
 
 
684
        print "it sure does!"
 
 
687
class cmd_assert_fail(Command):
 
 
688
    """Test reporting of assertion failures"""
 
 
691
        assert False, "always fails"
 
 
694
class cmd_help(Command):
 
 
695
    """Show help on a command or other topic.
 
 
697
    For a list of all available commands, say 'bzr help commands'."""
 
 
698
    takes_args = ['topic?']
 
 
700
    def run(self, topic=None):
 
 
704
def help(topic=None):
 
 
707
    elif topic == 'commands':
 
 
710
        help_on_command(topic)
 
 
713
def help_on_command(cmdname):
 
 
714
    cmdname = str(cmdname)
 
 
716
    from inspect import getdoc
 
 
717
    topic, cmdclass = get_cmd_class(cmdname)
 
 
719
    doc = getdoc(cmdclass)
 
 
721
        raise NotImplementedError("sorry, no detailed help yet for %r" % cmdname)
 
 
724
        short, rest = doc.split('\n', 1)
 
 
729
    print 'usage: bzr ' + topic,
 
 
730
    for aname in cmdclass.takes_args:
 
 
731
        aname = aname.upper()
 
 
732
        if aname[-1] in ['$', '+']:
 
 
733
            aname = aname[:-1] + '...'
 
 
734
        elif aname[-1] == '?':
 
 
735
            aname = '[' + aname[:-1] + ']'
 
 
736
        elif aname[-1] == '*':
 
 
737
            aname = '[' + aname[:-1] + '...]'
 
 
744
    help_on_option(cmdclass.takes_options)
 
 
747
def help_on_option(options):
 
 
755
        for shortname, longname in SHORT_OPTIONS.items():
 
 
757
                l += ', -' + shortname
 
 
763
    """List all commands"""
 
 
767
    for k, v in globals().items():
 
 
768
        if k.startswith('cmd_'):
 
 
769
            accu.append((k[4:].replace('_','-'), v))
 
 
771
    for cmdname, cmdclass in accu:
 
 
775
        help = inspect.getdoc(cmdclass)
 
 
777
            print "    " + help.split('\n', 1)[0]
 
 
780
######################################################################
 
 
784
# list of all available options; the rhs can be either None for an
 
 
785
# option that takes no argument, or a constructor function that checks
 
 
807
def parse_args(argv):
 
 
808
    """Parse command line.
 
 
810
    Arguments and options are parsed at this level before being passed
 
 
811
    down to specific command handlers.  This routine knows, from a
 
 
812
    lookup table, something about the available options, what optargs
 
 
813
    they take, and which commands will accept them.
 
 
815
    >>> parse_args('--help'.split())
 
 
817
    >>> parse_args('--version'.split())
 
 
818
    ([], {'version': True})
 
 
819
    >>> parse_args('status --all'.split())
 
 
820
    (['status'], {'all': True})
 
 
821
    >>> parse_args('commit --message=biter'.split())
 
 
822
    (['commit'], {'message': u'biter'})
 
 
827
    # TODO: Maybe handle '--' to end options?
 
 
832
            # option names must not be unicode
 
 
836
                mutter("  got option %r" % a)
 
 
838
                    optname, optarg = a[2:].split('=', 1)
 
 
841
                if optname not in OPTIONS:
 
 
842
                    bailout('unknown long option %r' % a)
 
 
845
                if shortopt not in SHORT_OPTIONS:
 
 
846
                    bailout('unknown short option %r' % a)
 
 
847
                optname = SHORT_OPTIONS[shortopt]
 
 
850
                # XXX: Do we ever want to support this, e.g. for -r?
 
 
851
                bailout('repeated option %r' % a)
 
 
853
            optargfn = OPTIONS[optname]
 
 
857
                        bailout('option %r needs an argument' % a)
 
 
860
                opts[optname] = optargfn(optarg)
 
 
863
                    bailout('option %r takes no argument' % optname)
 
 
873
def _match_argform(cmd, takes_args, args):
 
 
876
    # step through args and takes_args, allowing appropriate 0-many matches
 
 
877
    for ap in takes_args:
 
 
881
                argdict[argname] = args.pop(0)
 
 
882
        elif ap[-1] == '*': # all remaining arguments
 
 
884
                argdict[argname + '_list'] = args[:]
 
 
887
                argdict[argname + '_list'] = None
 
 
890
                raise BzrCommandError("command %r needs one or more %s"
 
 
891
                        % (cmd, argname.upper()))
 
 
893
                argdict[argname + '_list'] = args[:]
 
 
895
        elif ap[-1] == '$': # all but one
 
 
897
                raise BzrCommandError("command %r needs one or more %s"
 
 
898
                        % (cmd, argname.upper()))
 
 
899
            argdict[argname + '_list'] = args[:-1]
 
 
905
                raise BzrCommandError("command %r requires argument %s"
 
 
906
                        % (cmd, argname.upper()))
 
 
908
                argdict[argname] = args.pop(0)
 
 
911
        raise BzrCommandError("extra argument to command %s: %s"
 
 
919
    """Execute a command.
 
 
921
    This is similar to main(), but without all the trappings for
 
 
922
    logging and error handling.  
 
 
925
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
 
928
        args, opts = parse_args(argv[1:])
 
 
935
        elif 'version' in opts:
 
 
938
        cmd = str(args.pop(0))
 
 
940
        log_error('usage: bzr COMMAND')
 
 
941
        log_error('  try "bzr help"')
 
 
944
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
 
947
    if 'profile' in opts:
 
 
953
    # check options are reasonable
 
 
954
    allowed = cmd_class.takes_options
 
 
956
        if oname not in allowed:
 
 
957
            raise BzrCommandError("option %r is not allowed for command %r"
 
 
960
    # mix arguments and options into one dictionary
 
 
961
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
 
963
    for k, v in opts.items():
 
 
964
        cmdopts[k.replace('-', '_')] = v
 
 
968
        pffileno, pfname = tempfile.mkstemp()
 
 
970
            prof = hotshot.Profile(pfname)
 
 
971
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
 
975
            stats = hotshot.stats.load(pfname)
 
 
977
            stats.sort_stats('time')
 
 
978
            ## XXX: Might like to write to stderr or the trace file instead but
 
 
979
            ## print_stats seems hardcoded to stdout
 
 
980
            stats.print_stats(20)
 
 
988
        cmdobj = cmd_class(cmdopts, cmdargs) or 0
 
 
992
def _report_exception(e, summary, quiet=False):
 
 
994
    log_error('bzr: ' + summary)
 
 
995
    bzrlib.trace.log_exception(e)
 
 
998
        tb = sys.exc_info()[2]
 
 
999
        exinfo = traceback.extract_tb(tb)
 
 
1001
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
 
 
1002
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
 
 
1009
    bzrlib.trace.create_tracefile(argv)
 
 
1014
            # do this here to catch EPIPE
 
 
1018
            quiet = isinstance(e, (BzrCommandError))
 
 
1019
            _report_exception(e, 'error: ' + e.args[0], quiet=quiet)
 
 
1022
                    # some explanation or hints
 
 
1025
        except AssertionError, e:
 
 
1026
            msg = 'assertion failed'
 
 
1028
                msg += ': ' + str(e)
 
 
1029
            _report_exception(e, msg)
 
 
1031
        except KeyboardInterrupt, e:
 
 
1032
            _report_exception(e, 'interrupted', quiet=True)
 
 
1034
        except Exception, e:
 
 
1036
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
 
 
1040
                msg = str(e).rstrip('\n')
 
 
1041
            _report_exception(e, msg, quiet)
 
 
1044
        bzrlib.trace.close_trace()
 
 
1047
if __name__ == '__main__':
 
 
1048
    sys.exit(main(sys.argv))