~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-04 10:35:13 UTC
  • Revision ID: mbp@sourcefrog.net-20050404103513-d938ee5693d52989
merge win32 portability fixes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
2
 
 
3
 
 
4
1
# Copyright (C) 2004, 2005 by Martin Pool
5
2
# Copyright (C) 2005 by Canonical Ltd
6
3
 
28
25
* Metadata format is not stable yet -- you may need to
29
26
  discard history in the future.
30
27
 
31
 
* No handling of subdirectories, symlinks or any non-text files.
32
 
 
33
28
* Insufficient error handling.
34
29
 
35
30
* Many commands unimplemented or partially implemented.
40
35
 
41
36
Interesting commands::
42
37
 
43
 
  bzr help
44
 
       Show summary help screen
 
38
  bzr help [COMMAND]
 
39
       Show help screen
45
40
  bzr version
46
41
       Show software version/licence/non-warranty.
47
42
  bzr init
60
55
       Show summary of pending changes.
61
56
  bzr remove FILE...
62
57
       Make a file not versioned.
 
58
  bzr info
 
59
       Show statistics about this branch.
63
60
"""
64
61
 
65
 
# not currently working:
66
 
#  bzr check
67
 
#       Run internal consistency checks.
68
 
#  bzr info
69
 
#       Show some information about this branch.
70
 
 
71
 
 
72
 
 
73
 
__copyright__ = "Copyright 2005 Canonical Development Ltd."
74
 
__author__ = "Martin Pool <mbp@canonical.com>"
75
 
__docformat__ = "restructuredtext en"
76
 
__version__ = '0.0.0'
 
62
 
77
63
 
78
64
 
79
65
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
115
101
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
116
102
## to compare output?
117
103
 
118
 
 
119
 
 
120
 
 
121
 
######################################################################
122
 
# check status
 
104
## TODO: Some kind of global code to generate the right Branch object
 
105
## to work on.  Almost, but not quite all, commands need one, and it
 
106
## can be taken either from their parameters or their working
 
107
## directory.
 
108
 
 
109
## TODO: rename command, needed soon: check destination doesn't exist
 
110
## either in working copy or tree; move working copy; update
 
111
## inventory; write out
 
112
 
 
113
## TODO: move command; check destination is a directory and will not
 
114
## clash; move it.
 
115
 
 
116
## TODO: command to show renames, one per line, as to->from
 
117
 
 
118
 
123
119
 
124
120
 
125
121
def cmd_status(all=False):
141
137
    Branch('.').get_revision(revision_id).write_xml(sys.stdout)
142
138
 
143
139
 
144
 
def cmd_get_inventory(inventory_id):
145
 
    """Return inventory in XML by hash"""
146
 
    Branch('.').get_inventory(inventory_hash).write_xml(sys.stdout)
147
 
 
148
 
 
149
 
def cmd_get_revision_inventory(revision_id):
150
 
    """Output inventory for a revision."""
151
 
    b = Branch('.')
152
 
    b.get_revision_inventory(revision_id).write_xml(sys.stdout)
153
 
 
154
 
 
155
140
def cmd_get_file_text(text_id):
156
141
    """Get contents of a file by hash."""
157
142
    sf = Branch('.').text_store[text_id]
168
153
    print Branch('.').revno()
169
154
    
170
155
 
 
156
    
171
157
def cmd_add(file_list, verbose=False):
172
 
    """Add specified files.
 
158
    """Add specified files or directories.
 
159
 
 
160
    In non-recursive mode, all the named items are added, regardless
 
161
    of whether they were previously ignored.  A warning is given if
 
162
    any of the named files are already versioned.
 
163
 
 
164
    In recursive mode (the default), files are treated the same way
 
165
    but the behaviour for directories is different.  Directories that
 
166
    are already versioned do not give a warning.  All directories,
 
167
    whether already versioned or not, are searched for files or
 
168
    subdirectories that are neither versioned or ignored, and these
 
169
    are added.  This search proceeds recursively into versioned
 
170
    directories.
 
171
 
 
172
    Therefore simply saying 'bzr add .' will version all files that
 
173
    are currently unknown.
 
174
    """
 
175
    bzrlib.add.smart_add(file_list, verbose)
173
176
    
174
 
    Fails if the files are already added.
175
 
    """
176
 
    Branch('.').add(file_list, verbose=verbose)
 
177
 
 
178
def cmd_relpath(filename):
 
179
    """Show path of file relative to root"""
 
180
    print Branch(filename).relpath(filename)
 
181
 
177
182
 
178
183
 
179
184
def cmd_inventory(revision=None):
191
196
 
192
197
 
193
198
 
 
199
def cmd_mv(source_list, dest):
 
200
    b = Branch('.')
 
201
 
 
202
    b.rename([b.relpath(s) for s in source_list], b.relpath(dest))
 
203
 
 
204
 
 
205
 
194
206
def cmd_info():
195
 
    b = Branch('.')
196
 
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
197
 
 
198
 
    def plural(n, base='', pl=None):
199
 
        if n == 1:
200
 
            return base
201
 
        elif pl is not None:
202
 
            return pl
203
 
        else:
204
 
            return 's'
205
 
 
206
 
    count_version_dirs = 0
207
 
 
208
 
    count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
209
 
    for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
210
 
        fs = st_tup[0]
211
 
        count_status[fs] += 1
212
 
        if fs not in ['I', '?'] and st_tup[4] == 'directory':
213
 
            count_version_dirs += 1
214
 
 
215
 
    print
216
 
    print 'in the working tree:'
217
 
    for name, fs in (('unchanged', '.'),
218
 
                     ('modified', 'M'), ('added', 'A'), ('removed', 'D'),
219
 
                     ('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
220
 
                     ):
221
 
        print '  %5d %s' % (count_status[fs], name)
222
 
    print '  %5d versioned subdirector%s' % (count_version_dirs,
223
 
                                             plural(count_version_dirs, 'y', 'ies'))
224
 
 
225
 
    print
226
 
    print 'branch history:'
227
 
    history = b.revision_history()
228
 
    revno = len(history)
229
 
    print '  %5d revision%s' % (revno, plural(revno))
230
 
    committers = Set()
231
 
    for rev in history:
232
 
        committers.add(b.get_revision(rev).committer)
233
 
    print '  %5d committer%s' % (len(committers), plural(len(committers)))
234
 
    if revno > 0:
235
 
        firstrev = b.get_revision(history[0])
236
 
        age = int((time.time() - firstrev.timestamp) / 3600 / 24)
237
 
        print '  %5d day%s old' % (age, plural(age))
238
 
        print '  first revision: %s' % format_date(firstrev.timestamp,
239
 
                                                 firstrev.timezone)
240
 
 
241
 
        lastrev = b.get_revision(history[-1])
242
 
        print '  latest revision: %s' % format_date(lastrev.timestamp,
243
 
                                                    lastrev.timezone)
244
 
        
 
207
    """info: Show statistical information for this branch
 
208
 
 
209
usage: bzr info"""
 
210
    import info
 
211
    info.show_info(Branch('.'))        
245
212
    
246
213
 
247
214
 
248
215
def cmd_remove(file_list, verbose=False):
249
 
    Branch('.').remove(file_list, verbose=verbose)
 
216
    b = Branch(file_list[0])
 
217
    b.remove([b.relpath(f) for f in file_list], verbose=verbose)
250
218
 
251
219
 
252
220
 
253
221
def cmd_file_id(filename):
254
 
    i = Branch('.').read_working_inventory().path2id(filename)
 
222
    b = Branch(filename)
 
223
    i = b.inventory.path2id(b.relpath(filename))
255
224
    if i is None:
256
225
        bailout("%s is not a versioned file" % filename)
257
226
    else:
271
240
        print patchid
272
241
 
273
242
 
 
243
def cmd_directories():
 
244
    for name, ie in Branch('.').read_working_inventory().directories():
 
245
        if name == '':
 
246
            print '.'
 
247
        else:
 
248
            print name
 
249
 
 
250
 
 
251
def cmd_missing():
 
252
    for name, ie in Branch('.').working_tree().missing():
 
253
        print name
 
254
 
274
255
 
275
256
def cmd_init():
276
257
    # TODO: Check we're not already in a working directory?  At the
286
267
 
287
268
 
288
269
def cmd_diff(revision=None):
289
 
    """Show diff from basis to working copy.
290
 
 
291
 
    :todo: Take one or two revision arguments, look up those trees,
292
 
           and diff them.
293
 
 
294
 
    :todo: Allow diff across branches.
295
 
 
296
 
    :todo: Mangle filenames in diff to be more relevant.
297
 
 
298
 
    :todo: Shouldn't be in the cmd function.
299
 
    """
 
270
    """bzr diff: Show differences in working tree.
 
271
    
 
272
usage: bzr diff [-r REV]
 
273
 
 
274
--revision REV
 
275
    Show changes since REV, rather than predecessor.
 
276
 
 
277
TODO: Given two revision arguments, show the difference between them.
 
278
 
 
279
TODO: Allow diff across branches.
 
280
 
 
281
TODO: Option to use external diff command; could be GNU diff, wdiff,
 
282
or a graphical diff.
 
283
 
 
284
TODO: Diff selected files.
 
285
"""
 
286
 
 
287
    ## TODO: Shouldn't be in the cmd function.
300
288
 
301
289
    b = Branch('.')
302
290
 
333
321
        # FIXME: Something about the diff format makes patch unhappy
334
322
        # with newly-added files.
335
323
 
336
 
        def diffit(*a, **kw):
337
 
            sys.stdout.writelines(difflib.unified_diff(*a, **kw))
 
324
        def diffit(oldlines, newlines, **kw):
 
325
            # FIXME: difflib is wrong if there is no trailing newline.
 
326
 
 
327
            # Special workaround for Python2.3, where difflib fails if
 
328
            # both sequences are empty.
 
329
            if oldlines or newlines:
 
330
                sys.stdout.writelines(difflib.unified_diff(oldlines, newlines, **kw))
338
331
            print
339
332
        
340
333
        if file_state in ['.', '?', 'I']:
371
364
 
372
365
 
373
366
 
 
367
def cmd_deleted(show_ids=False):
 
368
    """List files deleted in the working tree.
 
369
 
 
370
TODO: Show files deleted since a previous revision, or between two revisions.
 
371
    """
 
372
    b = Branch('.')
 
373
    old = b.basis_tree()
 
374
    new = b.working_tree()
 
375
 
 
376
    ## TODO: Much more efficient way to do this: read in new
 
377
    ## directories with readdir, rather than stating each one.  Same
 
378
    ## level of effort but possibly much less IO.  (Or possibly not,
 
379
    ## if the directories are very large...)
 
380
 
 
381
    for path, ie in old.inventory.iter_entries():
 
382
        if not new.has_id(ie.file_id):
 
383
            if show_ids:
 
384
                print '%-50s %s' % (path, ie.file_id)
 
385
            else:
 
386
                print path
 
387
 
 
388
 
 
389
 
 
390
def cmd_parse_inventory():
 
391
    import cElementTree
 
392
    
 
393
    cElementTree.ElementTree().parse(file('.bzr/inventory'))
 
394
 
 
395
 
 
396
 
 
397
def cmd_load_inventory():
 
398
    inv = Branch('.').basis_tree().inventory
 
399
 
 
400
 
 
401
 
 
402
def cmd_dump_new_inventory():
 
403
    import bzrlib.newinventory
 
404
    inv = Branch('.').basis_tree().inventory
 
405
    bzrlib.newinventory.write_inventory(inv, sys.stdout)
 
406
 
 
407
 
 
408
def cmd_load_new_inventory():
 
409
    import bzrlib.newinventory
 
410
    bzrlib.newinventory.read_new_inventory(sys.stdin)
 
411
                
 
412
    
 
413
def cmd_dump_slacker_inventory():
 
414
    import bzrlib.newinventory
 
415
    inv = Branch('.').basis_tree().inventory
 
416
    bzrlib.newinventory.write_slacker_inventory(inv, sys.stdout)
 
417
                
 
418
    
 
419
 
 
420
def cmd_root(filename=None):
 
421
    """Print the branch root."""
 
422
    print bzrlib.branch.find_branch_root(filename)
 
423
    
 
424
 
374
425
def cmd_log(timezone='original'):
375
426
    """Show log of this branch.
376
427
 
411
462
        print quotefn(f)
412
463
 
413
464
 
 
465
 
 
466
def cmd_ignored(verbose=True):
 
467
    """List ignored files and the patterns that matched them.
 
468
      """
 
469
    tree = Branch('.').working_tree()
 
470
    for path, file_class, kind, id in tree.list_files():
 
471
        if file_class != 'I':
 
472
            continue
 
473
        ## XXX: Slightly inefficient since this was already calculated
 
474
        pat = tree.is_ignored(path)
 
475
        print '%-50s %s' % (path, pat)
 
476
 
 
477
 
414
478
def cmd_lookup_revision(revno):
415
479
    try:
416
480
        revno = int(revno)
437
501
 
438
502
def cmd_uuid():
439
503
    """Print a newly-generated UUID."""
440
 
    print uuid()
 
504
    print bzrlib.osutils.uuid()
441
505
 
442
506
 
443
507
 
446
510
 
447
511
 
448
512
 
449
 
def cmd_commit(message, verbose=False):
 
513
def cmd_commit(message=None, verbose=False):
 
514
    """Commit changes to a new revision.
 
515
 
 
516
--message MESSAGE
 
517
    Description of changes in this revision; free form text.
 
518
    It is recommended that the first line be a single-sentence
 
519
    summary.
 
520
--verbose
 
521
    Show status of changed files,
 
522
 
 
523
TODO: Commit only selected files.
 
524
 
 
525
TODO: Run hooks on tree to-be-committed, and after commit.
 
526
 
 
527
TODO: Strict commit that fails if there are unknown or deleted files.
 
528
"""
 
529
 
 
530
    if not message:
 
531
        bailout("please specify a commit message")
450
532
    Branch('.').commit(message, verbose=verbose)
451
533
 
452
534
 
453
 
def cmd_check():
454
 
    """Check consistency of the branch."""
455
 
    check()
 
535
def cmd_check(dir='.'):
 
536
    """check: Consistency check of branch history.
 
537
 
 
538
usage: bzr check [-v] [BRANCH]
 
539
 
 
540
options:
 
541
  --verbose, -v         Show progress of checking.
 
542
 
 
543
This command checks various invariants about the branch storage to
 
544
detect data corruption or bzr bugs.
 
545
"""
 
546
    import bzrlib.check
 
547
    bzrlib.check.check(Branch(dir, find_root=False))
456
548
 
457
549
 
458
550
def cmd_is(pred, *rest):
483
575
    print bzrlib.branch._gen_revision_id(time.time())
484
576
 
485
577
 
486
 
def cmd_doctest():
487
 
    """Run internal doctest suite"""
 
578
def cmd_selftest(verbose=False):
 
579
    """Run internal test suite"""
488
580
    ## -v, if present, is seen by doctest; the argument is just here
489
581
    ## so our parser doesn't complain
490
582
 
491
583
    ## TODO: --verbose option
 
584
 
 
585
    failures, tests = 0, 0
492
586
    
493
 
    import bzr, doctest, bzrlib.store
 
587
    import doctest, bzrlib.store, bzrlib.tests
494
588
    bzrlib.trace.verbose = False
495
 
    doctest.testmod(bzr)
496
 
    doctest.testmod(bzrlib.store)
497
 
    doctest.testmod(bzrlib.inventory)
498
 
    doctest.testmod(bzrlib.branch)
499
 
    doctest.testmod(bzrlib.osutils)
500
 
    doctest.testmod(bzrlib.tree)
501
 
 
502
 
    # more strenuous tests;
503
 
    import bzrlib.tests
504
 
    doctest.testmod(bzrlib.tests)
 
589
 
 
590
    for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
591
        bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
592
        mf, mt = doctest.testmod(m)
 
593
        failures += mf
 
594
        tests += mt
 
595
        print '%-40s %3d tests' % (m.__name__, mt),
 
596
        if mf:
 
597
            print '%3d FAILED!' % mf
 
598
        else:
 
599
            print
 
600
 
 
601
    print '%-40s %3d tests' % ('total', tests),
 
602
    if failures:
 
603
        print '%3d FAILED!' % failures
 
604
    else:
 
605
        print
 
606
 
 
607
 
 
608
 
 
609
# deprecated
 
610
cmd_doctest = cmd_selftest
505
611
 
506
612
 
507
613
######################################################################
508
614
# help
509
615
 
510
616
 
511
 
def cmd_help():
512
 
    # TODO: Specific help for particular commands
513
 
    print __doc__
 
617
def cmd_help(topic=None):
 
618
    if topic == None:
 
619
        print __doc__
 
620
        return
 
621
 
 
622
    # otherwise, maybe the name of a command?
 
623
    try:
 
624
        cmdfn = globals()['cmd_' + topic.replace('-', '_')]
 
625
    except KeyError:
 
626
        bailout("no help for %r" % topic)
 
627
 
 
628
    doc = cmdfn.__doc__
 
629
    if doc == None:
 
630
        bailout("sorry, no detailed help yet for %r" % topic)
 
631
 
 
632
    print doc
 
633
        
 
634
 
514
635
 
515
636
 
516
637
def cmd_version():
517
 
    print "bzr (bazaar-ng) %s" % __version__
518
 
    print __copyright__
 
638
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
639
    print bzrlib.__copyright__
519
640
    print "http://bazaar-ng.org/"
520
641
    print
521
642
    print \
541
662
    'all':                    None,
542
663
    'help':                   None,
543
664
    'message':                unicode,
 
665
    'profile':                None,
544
666
    'revision':               int,
545
667
    'show-ids':               None,
546
668
    'timezone':               str,
559
681
cmd_options = {
560
682
    'add':                    ['verbose'],
561
683
    'commit':                 ['message', 'verbose'],
 
684
    'deleted':                ['show-ids'],
562
685
    'diff':                   ['revision'],
563
686
    'inventory':              ['revision'],
564
 
    'log':                    ['show-ids', 'timezone'],
 
687
    'log':                    ['timezone'],
565
688
    'ls':                     ['revision', 'verbose'],
566
689
    'remove':                 ['verbose'],
567
690
    'status':                 ['all'],
569
692
 
570
693
 
571
694
cmd_args = {
572
 
    'init':                   [],
573
695
    'add':                    ['file+'],
574
696
    'commit':                 [],
575
697
    'diff':                   [],
 
698
    'export':                 ['revno', 'dest'],
576
699
    'file-id':                ['filename'],
577
700
    'get-file-text':          ['text_id'],
578
701
    'get-inventory':          ['inventory_id'],
579
702
    'get-revision':           ['revision_id'],
580
703
    'get-revision-inventory': ['revision_id'],
 
704
    'help':                   ['topic?'],
 
705
    'init':                   [],
581
706
    'log':                    [],
582
707
    'lookup-revision':        ['revno'],
583
 
    'export':                 ['revno', 'dest'],
 
708
    'mv':                     ['source$', 'dest'],
 
709
    'relpath':                ['filename'],
584
710
    'remove':                 ['file+'],
 
711
    'root':                   ['filename?'],
585
712
    'status':                 [],
586
713
    }
587
714
 
594
721
    lookup table, something about the available options, what optargs
595
722
    they take, and which commands will accept them.
596
723
 
597
 
    >>> parse_args('bzr --help'.split())
 
724
    >>> parse_args('--help'.split())
598
725
    ([], {'help': True})
599
 
    >>> parse_args('bzr --version'.split())
 
726
    >>> parse_args('--version'.split())
600
727
    ([], {'version': True})
601
 
    >>> parse_args('bzr status --all'.split())
 
728
    >>> parse_args('status --all'.split())
602
729
    (['status'], {'all': True})
603
 
    >>> parse_args('bzr commit --message=biter'.split())
 
730
    >>> parse_args('commit --message=biter'.split())
604
731
    (['commit'], {'message': u'biter'})
605
732
    """
606
733
    args = []
638
765
                    else:
639
766
                        optarg = argv.pop(0)
640
767
                opts[optname] = optargfn(optarg)
641
 
                mutter("    option argument %r" % opts[optname])
642
768
            else:
643
769
                if optarg != None:
644
770
                    bailout('option %r takes no argument' % optname)
668
794
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
669
795
    # all but one.
670
796
 
 
797
    # step through args and argform, allowing appropriate 0-many matches
671
798
    for ap in argform:
672
799
        argname = ap[:-1]
673
800
        if ap[-1] == '?':
674
 
            assert 0
 
801
            if args:
 
802
                argdict[argname] = args.pop(0)
675
803
        elif ap[-1] == '*':
676
804
            assert 0
677
805
        elif ap[-1] == '+':
681
809
            else:
682
810
                argdict[argname + '_list'] = args[:]
683
811
                args = []
 
812
        elif ap[-1] == '$': # all but one
 
813
            if len(args) < 2:
 
814
                bailout("command %r needs one or more %s"
 
815
                        % (cmd, argname.upper()))
 
816
            argdict[argname + '_list'] = args[:-1]
 
817
            args[:-1] = []                
684
818
        else:
685
819
            # just a plain arg
686
820
            argname = ap
709
843
        if 'help' in opts:
710
844
            # TODO: pass down other arguments in case they asked for
711
845
            # help on a command name?
712
 
            cmd_help()
 
846
            if args:
 
847
                cmd_help(args[0])
 
848
            else:
 
849
                cmd_help()
713
850
            return 0
714
851
        elif 'version' in opts:
715
852
            cmd_version()
719
856
        log_error('usage: bzr COMMAND\n')
720
857
        log_error('  try "bzr help"\n')
721
858
        return 1
722
 
            
 
859
 
723
860
    try:
724
861
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
725
862
    except KeyError:
726
863
        bailout("unknown command " + `cmd`)
727
864
 
728
 
    # TODO: special --profile option to turn on the Python profiler
 
865
    # global option
 
866
    if 'profile' in opts:
 
867
        profile = True
 
868
        del opts['profile']
 
869
    else:
 
870
        profile = False
729
871
 
730
872
    # check options are reasonable
731
873
    allowed = cmd_options.get(cmd, [])
734
876
            bailout("option %r is not allowed for command %r"
735
877
                    % (oname, cmd))
736
878
 
 
879
    # mix arguments and options into one dictionary
737
880
    cmdargs = _match_args(cmd, args)
738
 
    cmdargs.update(opts)
739
 
 
740
 
    ret = cmd_handler(**cmdargs) or 0
 
881
    for k, v in opts.items():
 
882
        cmdargs[k.replace('-', '_')] = v
 
883
 
 
884
    if profile:
 
885
        import hotshot
 
886
        prof = hotshot.Profile('.bzr.profile')
 
887
        ret = prof.runcall(cmd_handler, **cmdargs) or 0
 
888
        prof.close()
 
889
 
 
890
        import hotshot.stats
 
891
        stats = hotshot.stats.load('.bzr.profile')
 
892
        #stats.strip_dirs()
 
893
        stats.sort_stats('time')
 
894
        stats.print_stats(20)
 
895
    else:
 
896
        return cmd_handler(**cmdargs) or 0
741
897
 
742
898
 
743
899
 
748
904
    ## TODO: If the arguments are wrong, give a usage message rather
749
905
    ## than just a backtrace.
750
906
 
 
907
    bzrlib.trace.create_tracefile(argv)
 
908
    
751
909
    try:
752
 
        t = bzrlib.trace._tracefile
753
 
        t.write('-' * 60 + '\n')
754
 
        t.write('bzr invoked at %s\n' % format_date(time.time()))
755
 
        t.write('  by %s on %s\n' % (bzrlib.osutils.username(), socket.gethostname()))
756
 
        t.write('  arguments: %r\n' % argv)
757
 
 
758
 
        starttime = os.times()[4]
759
 
 
760
 
        import platform
761
 
        t.write('  platform: %s\n' % platform.platform())
762
 
        t.write('  python: %s\n' % platform.python_version())
763
 
 
764
910
        ret = run_bzr(argv)
765
 
        
766
 
        times = os.times()
767
 
        mutter("finished, %.3fu/%.3fs cpu, %.3fu/%.3fs cum"
768
 
               % times[:4])
769
 
        mutter("    %.3f elapsed" % (times[4] - starttime))
770
 
 
771
911
        return ret
772
912
    except BzrError, e:
773
913
        log_error('bzr: error: ' + e.args[0] + '\n')