~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-07 02:40:18 UTC
  • Revision ID: mbp@sourcefrog.net-20050407024018-cf7130ea991f4ebc0c353ed2
more notes on svk

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 info
67
 
#       Show some information about this branch.
68
 
 
69
 
 
70
 
 
71
 
__copyright__ = "Copyright 2005 Canonical Development Ltd."
72
 
__author__ = "Martin Pool <mbp@canonical.com>"
73
 
__docformat__ = "restructuredtext en"
74
 
__version__ = '0.0.0'
75
 
 
76
 
 
77
 
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
78
 
import traceback, socket, fnmatch, difflib
79
 
from os import path
 
62
 
 
63
 
 
64
 
 
65
import sys, os, time, types, shutil, tempfile, traceback, fnmatch, difflib, os.path
80
66
from sets import Set
81
67
from pprint import pprint
82
68
from stat import *
85
71
import bzrlib
86
72
from bzrlib.store import ImmutableStore
87
73
from bzrlib.trace import mutter, note, log_error
88
 
from bzrlib.errors import bailout, BzrError
 
74
from bzrlib.errors import bailout, BzrError, BzrCheckError
89
75
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
90
76
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
91
77
from bzrlib.revision import Revision
165
151
    print Branch('.').revno()
166
152
    
167
153
 
 
154
    
168
155
def cmd_add(file_list, verbose=False):
169
 
    """Add specified files.
 
156
    """Add specified files or directories.
 
157
 
 
158
    In non-recursive mode, all the named items are added, regardless
 
159
    of whether they were previously ignored.  A warning is given if
 
160
    any of the named files are already versioned.
 
161
 
 
162
    In recursive mode (the default), files are treated the same way
 
163
    but the behaviour for directories is different.  Directories that
 
164
    are already versioned do not give a warning.  All directories,
 
165
    whether already versioned or not, are searched for files or
 
166
    subdirectories that are neither versioned or ignored, and these
 
167
    are added.  This search proceeds recursively into versioned
 
168
    directories.
 
169
 
 
170
    Therefore simply saying 'bzr add .' will version all files that
 
171
    are currently unknown.
 
172
    """
 
173
    bzrlib.add.smart_add(file_list, verbose)
170
174
    
171
 
    Fails if the files are already added.
172
 
    """
173
 
    Branch('.').add(file_list, verbose=verbose)
 
175
 
 
176
def cmd_relpath(filename):
 
177
    """Show path of file relative to root"""
 
178
    print Branch(filename).relpath(filename)
 
179
 
174
180
 
175
181
 
176
182
def cmd_inventory(revision=None):
188
194
 
189
195
 
190
196
 
 
197
# TODO: Maybe a 'mv' command that has the combined move/rename
 
198
# special behaviour of Unix?
 
199
 
 
200
def cmd_move(source_list, dest):
 
201
    b = Branch('.')
 
202
 
 
203
    b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
204
 
 
205
 
 
206
 
 
207
def cmd_rename(from_name, to_name):
 
208
    """Change the name of an entry.
 
209
 
 
210
usage: bzr rename FROM_NAME TO_NAME
 
211
 
 
212
examples:
 
213
  bzr rename frob.c frobber.c
 
214
  bzr rename src/frob.c lib/frob.c
 
215
 
 
216
It is an error if the destination name exists.
 
217
 
 
218
See also the 'move' command, which moves files into a different
 
219
directory without changing their name.
 
220
 
 
221
TODO: Some way to rename multiple files without invoking bzr for each
 
222
one?"""
 
223
    b = Branch('.')
 
224
    b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
225
    
 
226
 
 
227
 
 
228
 
 
229
def cmd_renames(dir='.'):
 
230
    """Show list of renamed files.
 
231
 
 
232
usage: bzr renames [BRANCH]
 
233
 
 
234
TODO: Option to show renames between two historical versions.
 
235
 
 
236
TODO: Only show renames under dir, rather than in the whole branch.
 
237
"""
 
238
    b = Branch(dir)
 
239
    old_inv = b.basis_tree().inventory
 
240
    new_inv = b.read_working_inventory()
 
241
    
 
242
    renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
243
    renames.sort()
 
244
    for old_name, new_name in renames:
 
245
        print "%s => %s" % (old_name, new_name)        
 
246
 
 
247
 
 
248
 
191
249
def cmd_info():
192
 
    b = Branch('.')
193
 
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
194
 
 
195
 
    def plural(n, base='', pl=None):
196
 
        if n == 1:
197
 
            return base
198
 
        elif pl is not None:
199
 
            return pl
200
 
        else:
201
 
            return 's'
202
 
 
203
 
    count_version_dirs = 0
204
 
 
205
 
    count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
206
 
    for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
207
 
        fs = st_tup[0]
208
 
        count_status[fs] += 1
209
 
        if fs not in ['I', '?'] and st_tup[4] == 'directory':
210
 
            count_version_dirs += 1
211
 
 
212
 
    print
213
 
    print 'in the working tree:'
214
 
    for name, fs in (('unchanged', '.'),
215
 
                     ('modified', 'M'), ('added', 'A'), ('removed', 'D'),
216
 
                     ('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
217
 
                     ):
218
 
        print '  %5d %s' % (count_status[fs], name)
219
 
    print '  %5d versioned subdirector%s' % (count_version_dirs,
220
 
                                             plural(count_version_dirs, 'y', 'ies'))
221
 
 
222
 
    print
223
 
    print 'branch history:'
224
 
    history = b.revision_history()
225
 
    revno = len(history)
226
 
    print '  %5d revision%s' % (revno, plural(revno))
227
 
    committers = Set()
228
 
    for rev in history:
229
 
        committers.add(b.get_revision(rev).committer)
230
 
    print '  %5d committer%s' % (len(committers), plural(len(committers)))
231
 
    if revno > 0:
232
 
        firstrev = b.get_revision(history[0])
233
 
        age = int((time.time() - firstrev.timestamp) / 3600 / 24)
234
 
        print '  %5d day%s old' % (age, plural(age))
235
 
        print '  first revision: %s' % format_date(firstrev.timestamp,
236
 
                                                 firstrev.timezone)
237
 
 
238
 
        lastrev = b.get_revision(history[-1])
239
 
        print '  latest revision: %s' % format_date(lastrev.timestamp,
240
 
                                                    lastrev.timezone)
241
 
        
 
250
    """info: Show statistical information for this branch
 
251
 
 
252
usage: bzr info"""
 
253
    import info
 
254
    info.show_info(Branch('.'))        
242
255
    
243
256
 
244
257
 
245
258
def cmd_remove(file_list, verbose=False):
246
 
    Branch('.').remove(file_list, verbose=verbose)
 
259
    b = Branch(file_list[0])
 
260
    b.remove([b.relpath(f) for f in file_list], verbose=verbose)
247
261
 
248
262
 
249
263
 
250
264
def cmd_file_id(filename):
251
 
    i = Branch('.').read_working_inventory().path2id(filename)
252
 
    if i is None:
253
 
        bailout("%s is not a versioned file" % filename)
 
265
    """Print file_id of a particular file or directory.
 
266
 
 
267
usage: bzr file-id FILE
 
268
 
 
269
The file_id is assigned when the file is first added and remains the
 
270
same through all revisions where the file exists, even when it is
 
271
moved or renamed.
 
272
"""
 
273
    b = Branch(filename)
 
274
    i = b.inventory.path2id(b.relpath(filename))
 
275
    if i == None:
 
276
        bailout("%r is not a versioned file" % filename)
254
277
    else:
255
278
        print i
256
279
 
257
280
 
258
 
def cmd_find_filename(fileid):
259
 
    n = find_filename(fileid)
260
 
    if n is None:
261
 
        bailout("%s is not a live file id" % fileid)
262
 
    else:
263
 
        print n
 
281
def cmd_file_id_path(filename):
 
282
    """Print path of file_ids to a file or directory.
 
283
 
 
284
usage: bzr file-id-path FILE
 
285
 
 
286
This prints one line for each directory down to the target,
 
287
starting at the branch root."""
 
288
    b = Branch(filename)
 
289
    inv = b.inventory
 
290
    fid = inv.path2id(b.relpath(filename))
 
291
    if fid == None:
 
292
        bailout("%r is not a versioned file" % filename)
 
293
    for fip in inv.get_idpath(fid):
 
294
        print fip
264
295
 
265
296
 
266
297
def cmd_revision_history():
268
299
        print patchid
269
300
 
270
301
 
 
302
def cmd_directories():
 
303
    for name, ie in Branch('.').read_working_inventory().directories():
 
304
        if name == '':
 
305
            print '.'
 
306
        else:
 
307
            print name
 
308
 
 
309
 
 
310
def cmd_missing():
 
311
    for name, ie in Branch('.').working_tree().missing():
 
312
        print name
 
313
 
271
314
 
272
315
def cmd_init():
273
316
    # TODO: Check we're not already in a working directory?  At the
283
326
 
284
327
 
285
328
def cmd_diff(revision=None):
286
 
    """Show diff from basis to working copy.
287
 
 
288
 
    :todo: Take one or two revision arguments, look up those trees,
289
 
           and diff them.
290
 
 
291
 
    :todo: Allow diff across branches.
292
 
 
293
 
    :todo: Mangle filenames in diff to be more relevant.
294
 
 
295
 
    :todo: Shouldn't be in the cmd function.
296
 
    """
 
329
    """bzr diff: Show differences in working tree.
 
330
    
 
331
usage: bzr diff [-r REV]
 
332
 
 
333
--revision REV
 
334
    Show changes since REV, rather than predecessor.
 
335
 
 
336
TODO: Given two revision arguments, show the difference between them.
 
337
 
 
338
TODO: Allow diff across branches.
 
339
 
 
340
TODO: Option to use external diff command; could be GNU diff, wdiff,
 
341
or a graphical diff.
 
342
 
 
343
TODO: Diff selected files.
 
344
"""
 
345
 
 
346
    ## TODO: Shouldn't be in the cmd function.
297
347
 
298
348
    b = Branch('.')
299
349
 
303
353
        old_tree = b.revision_tree(b.lookup_revision(revision))
304
354
        
305
355
    new_tree = b.working_tree()
306
 
    old_inv = old_tree.inventory
307
 
    new_inv = new_tree.inventory
308
356
 
309
357
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
310
358
    old_label = ''
321
369
    # TODO: Better to return them in sorted order I think.
322
370
    
323
371
    for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
324
 
        d = None
325
 
 
326
372
        # Don't show this by default; maybe do it if an option is passed
327
373
        # idlabel = '      {%s}' % fid
328
374
        idlabel = ''
330
376
        # FIXME: Something about the diff format makes patch unhappy
331
377
        # with newly-added files.
332
378
 
333
 
        def diffit(*a, **kw):
334
 
            sys.stdout.writelines(difflib.unified_diff(*a, **kw))
 
379
        def diffit(oldlines, newlines, **kw):
 
380
            # FIXME: difflib is wrong if there is no trailing newline.
 
381
 
 
382
            # Special workaround for Python2.3, where difflib fails if
 
383
            # both sequences are empty.
 
384
            if oldlines or newlines:
 
385
                sys.stdout.writelines(difflib.unified_diff(oldlines, newlines, **kw))
335
386
            print
336
387
        
337
388
        if file_state in ['.', '?', 'I']:
368
419
 
369
420
 
370
421
 
 
422
def cmd_deleted(show_ids=False):
 
423
    """List files deleted in the working tree.
 
424
 
 
425
TODO: Show files deleted since a previous revision, or between two revisions.
 
426
    """
 
427
    b = Branch('.')
 
428
    old = b.basis_tree()
 
429
    new = b.working_tree()
 
430
 
 
431
    ## TODO: Much more efficient way to do this: read in new
 
432
    ## directories with readdir, rather than stating each one.  Same
 
433
    ## level of effort but possibly much less IO.  (Or possibly not,
 
434
    ## if the directories are very large...)
 
435
 
 
436
    for path, ie in old.inventory.iter_entries():
 
437
        if not new.has_id(ie.file_id):
 
438
            if show_ids:
 
439
                print '%-50s %s' % (path, ie.file_id)
 
440
            else:
 
441
                print path
 
442
 
 
443
 
 
444
 
 
445
def cmd_parse_inventory():
 
446
    import cElementTree
 
447
    
 
448
    cElementTree.ElementTree().parse(file('.bzr/inventory'))
 
449
 
 
450
 
 
451
 
 
452
def cmd_load_inventory():
 
453
    """Load inventory for timing purposes"""
 
454
    Branch('.').basis_tree().inventory
 
455
 
 
456
 
 
457
 
 
458
def cmd_dump_new_inventory():
 
459
    import bzrlib.newinventory
 
460
    inv = Branch('.').basis_tree().inventory
 
461
    bzrlib.newinventory.write_inventory(inv, sys.stdout)
 
462
 
 
463
 
 
464
def cmd_load_new_inventory():
 
465
    import bzrlib.newinventory
 
466
    bzrlib.newinventory.read_new_inventory(sys.stdin)
 
467
                
 
468
    
 
469
def cmd_dump_slacker_inventory():
 
470
    import bzrlib.newinventory
 
471
    inv = Branch('.').basis_tree().inventory
 
472
    bzrlib.newinventory.write_slacker_inventory(inv, sys.stdout)
 
473
                
 
474
    
 
475
 
 
476
def cmd_root(filename=None):
 
477
    """Print the branch root."""
 
478
    print bzrlib.branch.find_branch_root(filename)
 
479
    
 
480
 
371
481
def cmd_log(timezone='original'):
372
482
    """Show log of this branch.
373
483
 
408
518
        print quotefn(f)
409
519
 
410
520
 
 
521
 
 
522
def cmd_ignored():
 
523
    """List ignored files and the patterns that matched them.
 
524
      """
 
525
    tree = Branch('.').working_tree()
 
526
    for path, file_class, kind, file_id in tree.list_files():
 
527
        if file_class != 'I':
 
528
            continue
 
529
        ## XXX: Slightly inefficient since this was already calculated
 
530
        pat = tree.is_ignored(path)
 
531
        print '%-50s %s' % (path, pat)
 
532
 
 
533
 
411
534
def cmd_lookup_revision(revno):
412
535
    try:
413
536
        revno = int(revno)
426
549
    t = b.revision_tree(rh)
427
550
    t.export(dest)
428
551
 
 
552
def cmd_cat(revision, filename):
 
553
    """Print file to stdout."""
 
554
    b = Branch('.')
 
555
    b.print_file(b.relpath(filename), int(revision))
429
556
 
430
557
 
431
558
######################################################################
434
561
 
435
562
def cmd_uuid():
436
563
    """Print a newly-generated UUID."""
437
 
    print uuid()
 
564
    print bzrlib.osutils.uuid()
438
565
 
439
566
 
440
567
 
444
571
 
445
572
 
446
573
def cmd_commit(message=None, verbose=False):
 
574
    """Commit changes to a new revision.
 
575
 
 
576
--message MESSAGE
 
577
    Description of changes in this revision; free form text.
 
578
    It is recommended that the first line be a single-sentence
 
579
    summary.
 
580
--verbose
 
581
    Show status of changed files,
 
582
 
 
583
TODO: Commit only selected files.
 
584
 
 
585
TODO: Run hooks on tree to-be-committed, and after commit.
 
586
 
 
587
TODO: Strict commit that fails if there are unknown or deleted files.
 
588
"""
 
589
 
447
590
    if not message:
448
591
        bailout("please specify a commit message")
449
592
    Branch('.').commit(message, verbose=verbose)
450
593
 
451
594
 
452
 
def cmd_check():
453
 
    """Check consistency of the branch."""
454
 
    check()
 
595
def cmd_check(dir='.'):
 
596
    """check: Consistency check of branch history.
 
597
 
 
598
usage: bzr check [-v] [BRANCH]
 
599
 
 
600
options:
 
601
  --verbose, -v         Show progress of checking.
 
602
 
 
603
This command checks various invariants about the branch storage to
 
604
detect data corruption or bzr bugs.
 
605
"""
 
606
    import bzrlib.check
 
607
    bzrlib.check.check(Branch(dir, find_root=False))
455
608
 
456
609
 
457
610
def cmd_is(pred, *rest):
478
631
 
479
632
 
480
633
def cmd_gen_revision_id():
481
 
    import time
482
634
    print bzrlib.branch._gen_revision_id(time.time())
483
635
 
484
636
 
485
 
def cmd_selftest(verbose=False):
 
637
def cmd_selftest():
486
638
    """Run internal test suite"""
487
639
    ## -v, if present, is seen by doctest; the argument is just here
488
640
    ## so our parser doesn't complain
495
647
    bzrlib.trace.verbose = False
496
648
 
497
649
    for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
498
 
        bzrlib.tree, bzrlib.tests, bzrlib.commands:
 
650
        bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
499
651
        mf, mt = doctest.testmod(m)
500
652
        failures += mf
501
653
        tests += mt
521
673
# help
522
674
 
523
675
 
524
 
def cmd_help():
525
 
    # TODO: Specific help for particular commands
526
 
    print __doc__
 
676
def cmd_help(topic=None):
 
677
    if topic == None:
 
678
        print __doc__
 
679
        return
 
680
 
 
681
    # otherwise, maybe the name of a command?
 
682
    try:
 
683
        cmdfn = globals()['cmd_' + topic.replace('-', '_')]
 
684
    except KeyError:
 
685
        bailout("no help for %r" % topic)
 
686
 
 
687
    doc = cmdfn.__doc__
 
688
    if doc == None:
 
689
        bailout("sorry, no detailed help yet for %r" % topic)
 
690
 
 
691
    print doc
 
692
        
 
693
 
527
694
 
528
695
 
529
696
def cmd_version():
530
 
    print "bzr (bazaar-ng) %s" % __version__
531
 
    print __copyright__
 
697
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
698
    print bzrlib.__copyright__
532
699
    print "http://bazaar-ng.org/"
533
700
    print
534
701
    print \
554
721
    'all':                    None,
555
722
    'help':                   None,
556
723
    'message':                unicode,
 
724
    'profile':                None,
557
725
    'revision':               int,
558
726
    'show-ids':               None,
559
727
    'timezone':               str,
571
739
# listed take none.
572
740
cmd_options = {
573
741
    'add':                    ['verbose'],
 
742
    'cat':                    ['revision'],
574
743
    'commit':                 ['message', 'verbose'],
 
744
    'deleted':                ['show-ids'],
575
745
    'diff':                   ['revision'],
576
746
    'inventory':              ['revision'],
577
 
    'log':                    ['show-ids', 'timezone'],
 
747
    'log':                    ['timezone'],
578
748
    'ls':                     ['revision', 'verbose'],
579
749
    'remove':                 ['verbose'],
580
750
    'status':                 ['all'],
582
752
 
583
753
 
584
754
cmd_args = {
585
 
    'init':                   [],
586
755
    'add':                    ['file+'],
 
756
    'cat':                    ['filename'],
587
757
    'commit':                 [],
588
758
    'diff':                   [],
 
759
    'export':                 ['revno', 'dest'],
589
760
    'file-id':                ['filename'],
 
761
    'file-id-path':           ['filename'],
590
762
    'get-file-text':          ['text_id'],
591
763
    'get-inventory':          ['inventory_id'],
592
764
    'get-revision':           ['revision_id'],
593
765
    'get-revision-inventory': ['revision_id'],
 
766
    'help':                   ['topic?'],
 
767
    'init':                   [],
594
768
    'log':                    [],
595
769
    'lookup-revision':        ['revno'],
596
 
    'export':                 ['revno', 'dest'],
 
770
    'move':                   ['source$', 'dest'],
 
771
    'relpath':                ['filename'],
597
772
    'remove':                 ['file+'],
 
773
    'rename':                 ['from_name', 'to_name'],
 
774
    'renames':                ['dir?'],
 
775
    'root':                   ['filename?'],
598
776
    'status':                 [],
599
777
    }
600
778
 
651
829
                    else:
652
830
                        optarg = argv.pop(0)
653
831
                opts[optname] = optargfn(optarg)
654
 
                mutter("    option argument %r" % opts[optname])
655
832
            else:
656
833
                if optarg != None:
657
834
                    bailout('option %r takes no argument' % optname)
681
858
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
682
859
    # all but one.
683
860
 
 
861
    # step through args and argform, allowing appropriate 0-many matches
684
862
    for ap in argform:
685
863
        argname = ap[:-1]
686
864
        if ap[-1] == '?':
687
 
            assert 0
 
865
            if args:
 
866
                argdict[argname] = args.pop(0)
688
867
        elif ap[-1] == '*':
689
 
            assert 0
 
868
            raise BzrError("arg form %r not implemented yet" % ap)
690
869
        elif ap[-1] == '+':
691
870
            if not args:
692
871
                bailout("command %r needs one or more %s"
694
873
            else:
695
874
                argdict[argname + '_list'] = args[:]
696
875
                args = []
 
876
        elif ap[-1] == '$': # all but one
 
877
            if len(args) < 2:
 
878
                bailout("command %r needs one or more %s"
 
879
                        % (cmd, argname.upper()))
 
880
            argdict[argname + '_list'] = args[:-1]
 
881
            args[:-1] = []                
697
882
        else:
698
883
            # just a plain arg
699
884
            argname = ap
722
907
        if 'help' in opts:
723
908
            # TODO: pass down other arguments in case they asked for
724
909
            # help on a command name?
725
 
            cmd_help()
 
910
            if args:
 
911
                cmd_help(args[0])
 
912
            else:
 
913
                cmd_help()
726
914
            return 0
727
915
        elif 'version' in opts:
728
916
            cmd_version()
732
920
        log_error('usage: bzr COMMAND\n')
733
921
        log_error('  try "bzr help"\n')
734
922
        return 1
735
 
            
 
923
 
736
924
    try:
737
925
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
738
926
    except KeyError:
739
927
        bailout("unknown command " + `cmd`)
740
928
 
741
 
    # TODO: special --profile option to turn on the Python profiler
 
929
    # global option
 
930
    if 'profile' in opts:
 
931
        profile = True
 
932
        del opts['profile']
 
933
    else:
 
934
        profile = False
742
935
 
743
936
    # check options are reasonable
744
937
    allowed = cmd_options.get(cmd, [])
747
940
            bailout("option %r is not allowed for command %r"
748
941
                    % (oname, cmd))
749
942
 
 
943
    # TODO: give an error if there are any mandatory options which are
 
944
    # not specified?  Or maybe there shouldn't be any "mandatory
 
945
    # options" (it is an oxymoron)
 
946
 
 
947
    # mix arguments and options into one dictionary
750
948
    cmdargs = _match_args(cmd, args)
751
 
    cmdargs.update(opts)
752
 
 
753
 
    ret = cmd_handler(**cmdargs) or 0
 
949
    for k, v in opts.items():
 
950
        cmdargs[k.replace('-', '_')] = v
 
951
 
 
952
    if profile:
 
953
        import hotshot
 
954
        prof = hotshot.Profile('.bzr.profile')
 
955
        ret = prof.runcall(cmd_handler, **cmdargs) or 0
 
956
        prof.close()
 
957
 
 
958
        import hotshot.stats
 
959
        stats = hotshot.stats.load('.bzr.profile')
 
960
        #stats.strip_dirs()
 
961
        stats.sort_stats('time')
 
962
        stats.print_stats(20)
 
963
 
 
964
        return ret
 
965
    else:
 
966
        return cmd_handler(**cmdargs) or 0
754
967
 
755
968
 
756
969
 
771
984
        if len(e.args) > 1:
772
985
            for h in e.args[1]:
773
986
                log_error('  ' + h + '\n')
 
987
        traceback.print_exc(None, bzrlib.trace._tracefile)
 
988
        log_error('(see $HOME/.bzr.log for debug information)\n')
774
989
        return 1
775
990
    except Exception, e:
776
991
        log_error('bzr: exception: %s\n' % e)
777
 
        log_error('    see .bzr.log for details\n')
 
992
        log_error('(see $HOME/.bzr.log for debug information)\n')
778
993
        traceback.print_exc(None, bzrlib.trace._tracefile)
779
 
        traceback.print_exc(None, sys.stderr)
 
994
        ## traceback.print_exc(None, sys.stderr)
780
995
        return 1
781
996
 
782
997
    # TODO: Maybe nicer handling of IOError?