~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

merge from abently, take his fixes for merge in preference

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
 
19
 
import sys, os
 
18
# TODO: Split the command framework away from the actual commands.
 
19
 
 
20
# TODO: probably should say which arguments are candidates for glob
 
21
# expansion on windows and do that at the command level.
 
22
 
 
23
# TODO: Help messages for options.
 
24
 
 
25
# TODO: Define arguments by objects, rather than just using names.
 
26
# Those objects can specify the expected type of the argument, which
 
27
# would help with validation and shell completion.
 
28
 
 
29
 
 
30
# TODO: Help messages for options.
 
31
 
 
32
# TODO: Define arguments by objects, rather than just using names.
 
33
# Those objects can specify the expected type of the argument, which
 
34
# would help with validation and shell completion.
 
35
 
 
36
 
 
37
import sys
 
38
import os
20
39
 
21
40
import bzrlib
 
41
import bzrlib.trace
22
42
from bzrlib.trace import mutter, note, log_error, warning
23
43
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
24
44
from bzrlib.branch import find_branch
136
156
        raise BzrCommandError(msg)
137
157
    
138
158
 
 
159
def get_merge_type(typestring):
 
160
    """Attempt to find the merge class/factory associated with a string."""
 
161
    from merge import merge_types
 
162
    try:
 
163
        return merge_types[typestring][0]
 
164
    except KeyError:
 
165
        templ = '%s%%7s: %%s' % (' '*12)
 
166
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
167
        type_list = '\n'.join(lines)
 
168
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
169
            (typestring, type_list)
 
170
        raise BzrCommandError(msg)
 
171
    
 
172
 
139
173
 
140
174
def _get_cmd_dict(plugins_override=True):
141
175
    d = {}
237
271
class ExternalCommand(Command):
238
272
    """Class to wrap external commands.
239
273
 
240
 
    We cheat a little here, when get_cmd_class() calls us we actually give it back
241
 
    an object we construct that has the appropriate path, help, options etc for the
242
 
    specified command.
243
 
 
244
 
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
245
 
    method, which we override to call the Command.__init__ method. That then calls
246
 
    our run method which is pretty straight forward.
247
 
 
248
 
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
249
 
    back into command line options and arguments for the script.
 
274
    We cheat a little here, when get_cmd_class() calls us we actually
 
275
    give it back an object we construct that has the appropriate path,
 
276
    help, options etc for the specified command.
 
277
 
 
278
    When run_bzr() tries to instantiate that 'class' it gets caught by
 
279
    the __call__ method, which we override to call the Command.__init__
 
280
    method. That then calls our run method which is pretty straight
 
281
    forward.
 
282
 
 
283
    The only wrinkle is that we have to map bzr's dictionary of options
 
284
    and arguments back into command line options and arguments for the
 
285
    script.
250
286
    """
251
287
 
252
288
    def find_command(cls, cmd):
438
474
    takes_options = ['verbose', 'no-recurse']
439
475
    
440
476
    def run(self, file_list, verbose=False, no_recurse=False):
441
 
        from bzrlib.add import smart_add
442
 
        smart_add(file_list, verbose, not no_recurse)
 
477
        from bzrlib.add import smart_add, _PrintAddCallback
 
478
        smart_add(file_list, verbose, not no_recurse,
 
479
                  callback=_PrintAddCallback)
443
480
 
444
481
 
445
482
 
503
540
    def run(self, source_list, dest):
504
541
        b = find_branch('.')
505
542
 
 
543
        # TODO: glob expansion on windows?
506
544
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
507
545
 
508
546
 
528
566
 
529
567
 
530
568
 
 
569
class cmd_mv(Command):
 
570
    """Move or rename a file.
 
571
 
 
572
    usage:
 
573
        bzr mv OLDNAME NEWNAME
 
574
        bzr mv SOURCE... DESTINATION
 
575
 
 
576
    If the last argument is a versioned directory, all the other names
 
577
    are moved into it.  Otherwise, there must be exactly two arguments
 
578
    and the file is changed to a new name, which must not already exist.
 
579
 
 
580
    Files cannot be moved between branches.
 
581
    """
 
582
    takes_args = ['names*']
 
583
    def run(self, names_list):
 
584
        if len(names_list) < 2:
 
585
            raise BzrCommandError("missing file argument")
 
586
        b = find_branch(names_list[0])
 
587
 
 
588
        rel_names = [b.relpath(x) for x in names_list]
 
589
        
 
590
        if os.path.isdir(names_list[-1]):
 
591
            # move into existing directory
 
592
            b.move(rel_names[:-1], rel_names[-1])
 
593
        else:
 
594
            if len(names_list) != 2:
 
595
                raise BzrCommandError('to mv multiple files the destination '
 
596
                                      'must be a versioned directory')
 
597
            b.move(rel_names[0], rel_names[1])
 
598
            
 
599
    
531
600
 
532
601
 
533
602
class cmd_pull(Command):
551
620
        import tempfile
552
621
        from shutil import rmtree
553
622
        import errno
 
623
        from bzrlib.branch import pull_loc
554
624
        
555
625
        br_to = find_branch('.')
556
626
        stored_loc = None
600
670
    """
601
671
    takes_args = ['from_location', 'to_location?']
602
672
    takes_options = ['revision']
 
673
    aliases = ['get', 'clone']
603
674
 
604
675
    def run(self, from_location, to_location=None, revision=None):
 
676
        from bzrlib.branch import copy_branch, find_cached_branch
 
677
        import tempfile
605
678
        import errno
606
 
        from bzrlib.merge import merge
607
 
        from bzrlib.branch import DivergedBranches, NoSuchRevision, \
608
 
             find_cached_branch, Branch
609
679
        from shutil import rmtree
610
 
        from meta_store import CachedStore
611
 
        import tempfile
612
680
        cache_root = tempfile.mkdtemp()
613
 
 
614
 
        if revision is None:
615
 
            revision = [None]
616
 
        elif len(revision) > 1:
617
 
            raise BzrCommandError('bzr branch --revision takes exactly 1 revision value')
618
 
 
619
681
        try:
 
682
            if revision is None:
 
683
                revision = [None]
 
684
            elif len(revision) > 1:
 
685
                raise BzrCommandError(
 
686
                    'bzr branch --revision takes exactly 1 revision value')
620
687
            try:
621
688
                br_from = find_cached_branch(from_location, cache_root)
622
689
            except OSError, e:
625
692
                                          ' exist.' % to_location)
626
693
                else:
627
694
                    raise
628
 
 
629
695
            if to_location is None:
630
696
                to_location = os.path.basename(from_location.rstrip("/\\"))
631
 
 
632
697
            try:
633
698
                os.mkdir(to_location)
634
699
            except OSError, e:
640
705
                                          to_location)
641
706
                else:
642
707
                    raise
643
 
            br_to = Branch(to_location, init=True)
644
 
 
645
 
            br_to.set_root_id(br_from.get_root_id())
646
 
 
647
 
            if revision:
648
 
                if revision[0] is None:
649
 
                    revno = br_from.revno()
650
 
                else:
651
 
                    revno, rev_id = br_from.get_revision_info(revision[0])
652
 
                try:
653
 
                    br_to.update_revisions(br_from, stop_revision=revno)
654
 
                except NoSuchRevision:
655
 
                    rmtree(to_location)
656
 
                    msg = "The branch %s has no revision %d." % (from_location,
657
 
                                                                 revno)
658
 
                    raise BzrCommandError(msg)
659
 
            
660
 
            merge((to_location, -1), (to_location, 0), this_dir=to_location,
661
 
                  check_clean=False, ignore_zero=True)
662
 
            from_location = pull_loc(br_from)
663
 
            br_to.controlfile("x-pull", "wb").write(from_location + "\n")
 
708
            try:
 
709
                copy_branch(br_from, to_location, revision[0])
 
710
            except bzrlib.errors.NoSuchRevision:
 
711
                rmtree(to_location)
 
712
                msg = "The branch %s has no revision %d." % (from_location, revision[0])
 
713
                raise BzrCommandError(msg)
664
714
        finally:
665
715
            rmtree(cache_root)
666
716
 
667
717
 
668
 
def pull_loc(branch):
669
 
    # TODO: Should perhaps just make attribute be 'base' in
670
 
    # RemoteBranch and Branch?
671
 
    if hasattr(branch, "baseurl"):
672
 
        return branch.baseurl
673
 
    else:
674
 
        return branch.base
675
 
 
676
 
 
677
 
 
678
718
class cmd_renames(Command):
679
719
    """Show list of renamed files.
680
720
 
797
837
    If files are listed, only the changes in those files are listed.
798
838
    Otherwise, all changes for the tree are listed.
799
839
 
800
 
    TODO: Given two revision arguments, show the difference between them.
801
 
 
802
840
    TODO: Allow diff across branches.
803
841
 
804
842
    TODO: Option to use external diff command; could be GNU diff, wdiff,
813
851
          deleted files.
814
852
 
815
853
    TODO: This probably handles non-Unix newlines poorly.
 
854
 
 
855
    examples:
 
856
        bzr diff
 
857
        bzr diff -r1
 
858
        bzr diff -r1:2
816
859
    """
817
860
    
818
861
    takes_args = ['file*']
831
874
        else:
832
875
            b = find_branch('.')
833
876
 
834
 
        # TODO: Make show_diff support taking 2 arguments
835
 
        base_rev = None
836
877
        if revision is not None:
837
 
            if len(revision) != 1:
838
 
                raise BzrCommandError('bzr diff --revision takes exactly one revision identifier')
839
 
            base_rev = revision[0]
840
 
    
841
 
        show_diff(b, base_rev, specific_files=file_list,
842
 
                  external_diff_options=diff_options)
843
 
 
 
878
            if len(revision) == 1:
 
879
                show_diff(b, revision[0], specific_files=file_list,
 
880
                          external_diff_options=diff_options)
 
881
            elif len(revision) == 2:
 
882
                show_diff(b, revision[0], specific_files=file_list,
 
883
                          external_diff_options=diff_options,
 
884
                          revision2=revision[1])
 
885
            else:
 
886
                raise BzrCommandError('bzr diff --revision takes exactly one or two revision identifiers')
 
887
        else:
 
888
            show_diff(b, None, specific_files=file_list,
 
889
                      external_diff_options=diff_options)
844
890
 
845
891
        
846
892
 
872
918
    """List files modified in working tree."""
873
919
    hidden = True
874
920
    def run(self):
875
 
        from bzrlib.diff import compare_trees
 
921
        from bzrlib.delta import compare_trees
876
922
 
877
923
        b = find_branch('.')
878
924
        td = compare_trees(b.basis_tree(), b.working_tree())
927
973
    """
928
974
 
929
975
    takes_args = ['filename?']
930
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision','long', 'message']
 
976
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision',
 
977
                     'long', 'message', 'short',]
931
978
    
932
979
    def run(self, filename=None, timezone='original',
933
980
            verbose=False,
935
982
            forward=False,
936
983
            revision=None,
937
984
            message=None,
938
 
            long=False):
 
985
            long=False,
 
986
            short=False):
939
987
        from bzrlib.branch import find_branch
940
988
        from bzrlib.log import log_formatter, show_log
941
989
        import codecs
975
1023
        # in e.g. the default C locale.
976
1024
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
977
1025
 
978
 
        if long:
 
1026
        if not short:
979
1027
            log_format = 'long'
980
1028
        else:
981
1029
            log_format = 'short'
1198
1246
 
1199
1247
class cmd_commit(Command):
1200
1248
    """Commit changes into a new revision.
 
1249
    
 
1250
    If no arguments are given, the entire tree is committed.
1201
1251
 
1202
1252
    If selected files are specified, only changes to those files are
1203
 
    committed.  If a directory is specified then its contents are also
1204
 
    committed.
 
1253
    committed.  If a directory is specified then the directory and everything 
 
1254
    within it is committed.
1205
1255
 
1206
1256
    A selected-file commit may fail in some cases where the committed
1207
1257
    tree would be invalid, such as trying to commit a file in a
1215
1265
    takes_options = ['message', 'file', 'verbose', 'unchanged']
1216
1266
    aliases = ['ci', 'checkin']
1217
1267
 
 
1268
    # TODO: Give better message for -s, --summary, used by tla people
 
1269
    
1218
1270
    def run(self, message=None, file=None, verbose=True, selected_list=None,
1219
1271
            unchanged=False):
1220
1272
        from bzrlib.errors import PointlessCommit
1222
1274
 
1223
1275
        ## Warning: shadows builtin file()
1224
1276
        if not message and not file:
 
1277
            # FIXME: Ugly; change status code to send to a provided function?
 
1278
            
1225
1279
            import cStringIO
1226
1280
            stdout = sys.stdout
1227
1281
            catcher = cStringIO.StringIO()
1242
1296
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1243
1297
 
1244
1298
        b = find_branch('.')
1245
 
 
 
1299
        if selected_list:
 
1300
            selected_list = [b.relpath(s) for s in selected_list]
 
1301
            
1246
1302
        try:
1247
1303
            b.commit(message, verbose=verbose,
1248
1304
                     specific_files=selected_list,
1267
1323
 
1268
1324
    def run(self, dir='.'):
1269
1325
        from bzrlib.check import check
 
1326
 
1270
1327
        check(find_branch(dir))
1271
1328
 
1272
1329
 
1273
 
 
1274
1330
class cmd_scan_cache(Command):
1275
1331
    hidden = True
1276
1332
    def run(self):
1311
1367
    takes_options = ['email']
1312
1368
    
1313
1369
    def run(self, email=False):
 
1370
        try:
 
1371
            b = bzrlib.branch.find_branch('.')
 
1372
        except:
 
1373
            b = None
 
1374
        
1314
1375
        if email:
1315
 
            print bzrlib.osutils.user_email()
 
1376
            print bzrlib.osutils.user_email(b)
1316
1377
        else:
1317
 
            print bzrlib.osutils.username()
 
1378
            print bzrlib.osutils.username(b)
1318
1379
 
1319
1380
 
1320
1381
class cmd_selftest(Command):
1321
1382
    """Run internal test suite"""
1322
1383
    hidden = True
1323
 
    takes_options = ['verbose']
1324
 
    def run(self, verbose=False):
 
1384
    takes_options = ['verbose', 'pattern']
 
1385
    def run(self, verbose=False, pattern=".*"):
 
1386
        import bzrlib.ui
1325
1387
        from bzrlib.selftest import selftest
1326
 
        return int(not selftest(verbose=verbose))
 
1388
        # we don't want progress meters from the tests to go to the
 
1389
        # real output; and we don't want log messages cluttering up
 
1390
        # the real logs.
 
1391
        save_ui = bzrlib.ui.ui_factory
 
1392
        bzrlib.trace.info('running tests...')
 
1393
        bzrlib.trace.disable_default_logging()
 
1394
        try:
 
1395
            bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
 
1396
            result = selftest(verbose=verbose, pattern=pattern)
 
1397
            if result:
 
1398
                bzrlib.trace.info('tests passed')
 
1399
            else:
 
1400
                bzrlib.trace.info('tests failed')
 
1401
            return int(not result)
 
1402
        finally:
 
1403
            bzrlib.trace.enable_default_logging()
 
1404
            bzrlib.ui.ui_factory = save_ui
1327
1405
 
1328
1406
 
1329
1407
class cmd_version(Command):
1384
1462
 
1385
1463
 
1386
1464
 
 
1465
class cmd_find_merge_base(Command):
 
1466
    """Find and print a base revision for merging two branches.
 
1467
 
 
1468
    TODO: Options to specify revisions on either side, as if
 
1469
          merging only part of the history.
 
1470
    """
 
1471
    takes_args = ['branch', 'other']
 
1472
    hidden = True
 
1473
    
 
1474
    def run(self, branch, other):
 
1475
        branch1 = find_branch(branch)
 
1476
        branch2 = find_branch(other)
 
1477
 
 
1478
        base_revno, base_revid = branch1.common_ancestor(branch2)
 
1479
 
 
1480
        if base_revno is None:
 
1481
            raise bzrlib.errors.UnrelatedBranches()
 
1482
 
 
1483
        print 'merge base is revision %s' % base_revid
 
1484
        print ' r%-6d in %s' % (base_revno, branch)
 
1485
 
 
1486
        other_revno = branch2.revision_id_to_revno(base_revid)
 
1487
        
 
1488
        print ' r%-6d in %s' % (other_revno, other)
 
1489
 
 
1490
 
 
1491
 
1387
1492
class cmd_merge(Command):
1388
 
    """Perform a three-way merge of trees.
1389
 
    
1390
 
    The SPEC parameters are working tree or revision specifiers.  Working trees
1391
 
    are specified using standard paths or urls.  No component of a directory
1392
 
    path may begin with '@'.
1393
 
    
1394
 
    Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
1395
 
 
1396
 
    Revisions are specified using a dirname/@revno pair, where dirname is the
1397
 
    branch directory and revno is the revision within that branch.  If no revno
1398
 
    is specified, the latest revision is used.
1399
 
 
1400
 
    Revision examples: './@127', 'foo/@', '../@1'
1401
 
 
1402
 
    The OTHER_SPEC parameter is required.  If the BASE_SPEC parameter is
1403
 
    not supplied, the common ancestor of OTHER_SPEC the current branch is used
1404
 
    as the BASE.
1405
 
 
 
1493
    """Perform a three-way merge.
 
1494
    
 
1495
    The branch is the branch you will merge from.  By default, it will merge
 
1496
    the latest revision.  If you specify a revision, that revision will be
 
1497
    merged.  If you specify two revisions, the first will be used as a BASE, 
 
1498
    and the second one as OTHER.  Revision numbers are always relative to the
 
1499
    specified branch.
 
1500
    
 
1501
    Examples:
 
1502
 
 
1503
    To merge the latest revision from bzr.dev
 
1504
    bzr merge ../bzr.dev
 
1505
 
 
1506
    To merge changes up to and including revision 82 from bzr.dev
 
1507
    bzr merge -r 82 ../bzr.dev
 
1508
 
 
1509
    To merge the changes introduced by 82, without previous changes:
 
1510
    bzr merge -r 81..82 ../bzr.dev
 
1511
    
1406
1512
    merge refuses to run if there are any uncommitted changes, unless
1407
1513
    --force is given.
1408
1514
    """
1409
 
    takes_args = ['other_spec', 'base_spec?']
1410
 
    takes_options = ['force', 'merge-type']
 
1515
    takes_args = ['branch?']
 
1516
    takes_options = ['revision', 'force', 'merge-type']
1411
1517
 
1412
 
    def run(self, other_spec, base_spec=None, force=False, merge_type=None):
 
1518
    def run(self, branch='.', revision=None, force=False, 
 
1519
            merge_type=None):
1413
1520
        from bzrlib.merge import merge
1414
1521
        from bzrlib.merge_core import ApplyMerge3
1415
1522
        if merge_type is None:
1416
1523
            merge_type = ApplyMerge3
1417
 
        merge(parse_spec(other_spec), parse_spec(base_spec),
1418
 
              check_clean=(not force), merge_type=merge_type)
 
1524
 
 
1525
        if revision is None or len(revision) < 1:
 
1526
            base = [None, None]
 
1527
            other = (branch, -1)
 
1528
        else:
 
1529
            if len(revision) == 1:
 
1530
                other = (branch, revision[0])
 
1531
                base = (None, None)
 
1532
            else:
 
1533
                assert len(revision) == 2
 
1534
                if None in revision:
 
1535
                    raise BzrCommandError(
 
1536
                        "Merge doesn't permit that revision specifier.")
 
1537
                base = (branch, revision[0])
 
1538
                other = (branch, revision[1])
 
1539
            
 
1540
        merge(other, base, check_clean=(not force), merge_type=merge_type)
1419
1541
 
1420
1542
 
1421
1543
class cmd_revert(Command):
1431
1553
 
1432
1554
    def run(self, revision=None, no_backup=False, file_list=None):
1433
1555
        from bzrlib.merge import merge
 
1556
        from bzrlib.branch import Branch
1434
1557
        if file_list is not None:
1435
1558
            if len(file_list) == 0:
1436
1559
                raise BzrCommandError("No files specified")
1443
1566
              ignore_zero=True,
1444
1567
              backup_files=not no_backup,
1445
1568
              file_list=file_list)
 
1569
        if not file_list:
 
1570
            Branch('.').set_pending_merges([])
1446
1571
 
1447
1572
 
1448
1573
class cmd_assert_fail(Command):
1456
1581
    """Show help on a command or other topic.
1457
1582
 
1458
1583
    For a list of all available commands, say 'bzr help commands'."""
 
1584
    takes_options = ['long']
1459
1585
    takes_args = ['topic?']
1460
1586
    aliases = ['?']
1461
1587
    
1462
 
    def run(self, topic=None):
 
1588
    def run(self, topic=None, long=False):
1463
1589
        import help
 
1590
        if topic is None and long:
 
1591
            topic = "commands"
1464
1592
        help.help(topic)
1465
1593
 
1466
1594
 
 
1595
class cmd_shell_complete(Command):
 
1596
    """Show appropriate completions for context.
 
1597
 
 
1598
    For a list of all available commands, say 'bzr shell-complete'."""
 
1599
    takes_args = ['context?']
 
1600
    aliases = ['s-c']
 
1601
    hidden = True
 
1602
    
 
1603
    def run(self, context=None):
 
1604
        import shellcomplete
 
1605
        shellcomplete.shellcomplete(context)
 
1606
 
 
1607
 
 
1608
class cmd_missing(Command):
 
1609
    """What is missing in this branch relative to other branch.
 
1610
    """
 
1611
    takes_args = ['remote?']
 
1612
    aliases = ['mis', 'miss']
 
1613
    # We don't have to add quiet to the list, because 
 
1614
    # unknown options are parsed as booleans
 
1615
    takes_options = ['verbose', 'quiet']
 
1616
 
 
1617
    def run(self, remote=None, verbose=False, quiet=False):
 
1618
        from bzrlib.branch import find_branch, DivergedBranches
 
1619
        from bzrlib.errors import BzrCommandError
 
1620
        from bzrlib.missing import get_parent, show_missing
 
1621
 
 
1622
        if verbose and quiet:
 
1623
            raise BzrCommandError('Cannot pass both quiet and verbose')
 
1624
 
 
1625
        b = find_branch('.')
 
1626
        parent = get_parent(b)
 
1627
        if remote is None:
 
1628
            if parent is None:
 
1629
                raise BzrCommandError("No missing location known or specified.")
 
1630
            else:
 
1631
                if not quiet:
 
1632
                    print "Using last location: %s" % parent
 
1633
                remote = parent
 
1634
        elif parent is None:
 
1635
            # We only update x-pull if it did not exist, missing should not change the parent
 
1636
            b.controlfile('x-pull', 'wb').write(remote + '\n')
 
1637
        br_remote = find_branch(remote)
 
1638
 
 
1639
        return show_missing(b, br_remote, verbose=verbose, quiet=quiet)
 
1640
 
1467
1641
 
1468
1642
 
1469
1643
class cmd_plugins(Command):
1498
1672
    'no-recurse':             None,
1499
1673
    'profile':                None,
1500
1674
    'revision':               _parse_revision_str,
 
1675
    'short':                  None,
1501
1676
    'show-ids':               None,
1502
1677
    'timezone':               str,
1503
1678
    'verbose':                None,
1509
1684
    'root':                   str,
1510
1685
    'no-backup':              None,
1511
1686
    'merge-type':             get_merge_type,
 
1687
    'pattern':                str,
1512
1688
    }
1513
1689
 
1514
1690
SHORT_OPTIONS = {
1664
1840
    return argdict
1665
1841
 
1666
1842
 
1667
 
def _parse_master_args(argv):
1668
 
    """Parse the arguments that always go with the original command.
1669
 
    These are things like bzr --no-plugins, etc.
1670
 
 
1671
 
    There are now 2 types of option flags. Ones that come *before* the command,
1672
 
    and ones that come *after* the command.
1673
 
    Ones coming *before* the command are applied against all possible commands.
1674
 
    And are generally applied before plugins are loaded.
1675
 
 
1676
 
    The current list are:
1677
 
        --builtin   Allow plugins to load, but don't let them override builtin commands,
1678
 
                    they will still be allowed if they do not override a builtin.
1679
 
        --no-plugins    Don't load any plugins. This lets you get back to official source
1680
 
                        behavior.
1681
 
        --profile   Enable the hotspot profile before running the command.
1682
 
                    For backwards compatibility, this is also a non-master option.
1683
 
        --version   Spit out the version of bzr that is running and exit.
1684
 
                    This is also a non-master option.
1685
 
        --help      Run help and exit, also a non-master option (I think that should stay, though)
1686
 
 
1687
 
    >>> argv, opts = _parse_master_args(['--test'])
1688
 
    Traceback (most recent call last):
1689
 
    ...
1690
 
    BzrCommandError: Invalid master option: 'test'
1691
 
    >>> argv, opts = _parse_master_args(['--version', 'command'])
1692
 
    >>> print argv
1693
 
    ['command']
1694
 
    >>> print opts['version']
1695
 
    True
1696
 
    >>> argv, opts = _parse_master_args(['--profile', 'command', '--more-options'])
1697
 
    >>> print argv
1698
 
    ['command', '--more-options']
1699
 
    >>> print opts['profile']
1700
 
    True
1701
 
    >>> argv, opts = _parse_master_args(['--no-plugins', 'command'])
1702
 
    >>> print argv
1703
 
    ['command']
1704
 
    >>> print opts['no-plugins']
1705
 
    True
1706
 
    >>> print opts['profile']
1707
 
    False
1708
 
    >>> argv, opts = _parse_master_args(['command', '--profile'])
1709
 
    >>> print argv
1710
 
    ['command', '--profile']
1711
 
    >>> print opts['profile']
1712
 
    False
1713
 
    """
1714
 
    master_opts = {'builtin':False,
1715
 
        'no-plugins':False,
1716
 
        'version':False,
1717
 
        'profile':False,
1718
 
        'help':False
1719
 
    }
1720
 
 
1721
 
    for arg in argv[:]:
1722
 
        if arg[:2] != '--': # at the first non-option, we return the rest
1723
 
            break
1724
 
        arg = arg[2:] # Remove '--'
1725
 
        if arg not in master_opts:
1726
 
            # We could say that this is not an error, that we should
1727
 
            # just let it be handled by the main section instead
1728
 
            raise BzrCommandError('Invalid master option: %r' % arg)
1729
 
        argv.pop(0) # We are consuming this entry
1730
 
        master_opts[arg] = True
1731
 
    return argv, master_opts
1732
 
 
1733
 
 
1734
1843
 
1735
1844
def run_bzr(argv):
1736
1845
    """Execute a command.
1739
1848
    logging and error handling.  
1740
1849
    
1741
1850
    argv
1742
 
       The command-line arguments, without the program name.
 
1851
       The command-line arguments, without the program name from argv[0]
1743
1852
    
1744
1853
    Returns a command status or raises an exception.
 
1854
 
 
1855
    Special master options: these must come before the command because
 
1856
    they control how the command is interpreted.
 
1857
 
 
1858
    --no-plugins
 
1859
        Do not load plugin modules at all
 
1860
 
 
1861
    --builtin
 
1862
        Only use builtin commands.  (Plugins are still allowed to change
 
1863
        other behaviour.)
 
1864
 
 
1865
    --profile
 
1866
        Run under the Python profiler.
1745
1867
    """
 
1868
    
1746
1869
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
1747
1870
 
1748
 
    # some options like --builtin and --no-plugins have special effects
1749
 
    argv, master_opts = _parse_master_args(argv)
1750
 
    if not master_opts['no-plugins']:
 
1871
    opt_profile = opt_no_plugins = opt_builtin = False
 
1872
 
 
1873
    # --no-plugins is handled specially at a very early stage. We need
 
1874
    # to load plugins before doing other command parsing so that they
 
1875
    # can override commands, but this needs to happen first.
 
1876
 
 
1877
    for a in argv[:]:
 
1878
        if a == '--profile':
 
1879
            opt_profile = True
 
1880
        elif a == '--no-plugins':
 
1881
            opt_no_plugins = True
 
1882
        elif a == '--builtin':
 
1883
            opt_builtin = True
 
1884
        else:
 
1885
            break
 
1886
        argv.remove(a)
 
1887
 
 
1888
    if not opt_no_plugins:
1751
1889
        from bzrlib.plugin import load_plugins
1752
1890
        load_plugins()
1753
1891
 
1754
1892
    args, opts = parse_args(argv)
1755
1893
 
1756
 
    if master_opts.get('help') or 'help' in opts:
 
1894
    if 'help' in opts:
1757
1895
        from bzrlib.help import help
1758
 
        if argv:
1759
 
            help(argv[0])
 
1896
        if args:
 
1897
            help(args[0])
1760
1898
        else:
1761
1899
            help()
1762
1900
        return 0            
1765
1903
        show_version()
1766
1904
        return 0
1767
1905
    
1768
 
    if args and args[0] == 'builtin':
1769
 
        include_plugins=False
1770
 
        args = args[1:]
 
1906
    if not args:
 
1907
        from bzrlib.help import help
 
1908
        help(None)
 
1909
        return 0
1771
1910
    
1772
 
    try:
1773
 
        cmd = str(args.pop(0))
1774
 
    except IndexError:
1775
 
        print >>sys.stderr, "please try 'bzr help' for help"
1776
 
        return 1
1777
 
 
1778
 
    plugins_override = not (master_opts['builtin'])
1779
 
    canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
1780
 
 
1781
 
    profile = master_opts['profile']
1782
 
    # For backwards compatibility, I would rather stick with --profile being a
1783
 
    # master/global option
1784
 
    if 'profile' in opts:
1785
 
        profile = True
1786
 
        del opts['profile']
 
1911
    cmd = str(args.pop(0))
 
1912
 
 
1913
    canonical_cmd, cmd_class = \
 
1914
                   get_cmd_class(cmd, plugins_override=not opt_builtin)
1787
1915
 
1788
1916
    # check options are reasonable
1789
1917
    allowed = cmd_class.takes_options
1798
1926
    for k, v in opts.items():
1799
1927
        cmdopts[k.replace('-', '_')] = v
1800
1928
 
1801
 
    if profile:
 
1929
    if opt_profile:
1802
1930
        import hotshot, tempfile
1803
1931
        pffileno, pfname = tempfile.mkstemp()
1804
1932
        try:
1823
1951
        return cmd_class(cmdopts, cmdargs).status 
1824
1952
 
1825
1953
 
1826
 
def _report_exception(summary, quiet=False):
1827
 
    import traceback
1828
 
    log_error('bzr: ' + summary)
1829
 
    bzrlib.trace.log_exception()
1830
 
 
1831
 
    if not quiet:
1832
 
        tb = sys.exc_info()[2]
1833
 
        exinfo = traceback.extract_tb(tb)
1834
 
        if exinfo:
1835
 
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
1836
 
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
1837
 
 
1838
 
 
1839
 
 
1840
1954
def main(argv):
1841
 
    
1842
 
    bzrlib.trace.open_tracefile(argv)
 
1955
    import bzrlib.ui
 
1956
    bzrlib.trace.log_startup(argv)
 
1957
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
1843
1958
 
1844
1959
    try:
1845
1960
        try:
1846
 
            try:
1847
 
                return run_bzr(argv[1:])
1848
 
            finally:
1849
 
                # do this here inside the exception wrappers to catch EPIPE
1850
 
                sys.stdout.flush()
1851
 
        except BzrError, e:
1852
 
            quiet = isinstance(e, (BzrCommandError))
1853
 
            _report_exception('error: ' + e.args[0], quiet=quiet)
1854
 
            if len(e.args) > 1:
1855
 
                for h in e.args[1]:
1856
 
                    # some explanation or hints
1857
 
                    log_error('  ' + h)
1858
 
            return 1
1859
 
        except AssertionError, e:
1860
 
            msg = 'assertion failed'
1861
 
            if str(e):
1862
 
                msg += ': ' + str(e)
1863
 
            _report_exception(msg)
1864
 
            return 2
1865
 
        except KeyboardInterrupt, e:
1866
 
            _report_exception('interrupted', quiet=True)
1867
 
            return 2
1868
 
        except Exception, e:
1869
 
            import errno
1870
 
            quiet = False
1871
 
            if (isinstance(e, IOError) 
1872
 
                and hasattr(e, 'errno')
1873
 
                and e.errno == errno.EPIPE):
1874
 
                quiet = True
1875
 
                msg = 'broken pipe'
1876
 
            else:
1877
 
                msg = str(e).rstrip('\n')
1878
 
            _report_exception(msg, quiet)
1879
 
            return 2
1880
 
    finally:
1881
 
        bzrlib.trace.close_trace()
 
1961
            return run_bzr(argv[1:])
 
1962
        finally:
 
1963
            # do this here inside the exception wrappers to catch EPIPE
 
1964
            sys.stdout.flush()
 
1965
    except BzrCommandError, e:
 
1966
        # command line syntax error, etc
 
1967
        log_error(str(e))
 
1968
        return 1
 
1969
    except BzrError, e:
 
1970
        bzrlib.trace.log_exception()
 
1971
        return 1
 
1972
    except AssertionError, e:
 
1973
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
 
1974
        return 3
 
1975
    except KeyboardInterrupt, e:
 
1976
        bzrlib.trace.note('interrupted')
 
1977
        return 2
 
1978
    except Exception, e:
 
1979
        import errno
 
1980
        if (isinstance(e, IOError) 
 
1981
            and hasattr(e, 'errno')
 
1982
            and e.errno == errno.EPIPE):
 
1983
            bzrlib.trace.note('broken pipe')
 
1984
            return 2
 
1985
        else:
 
1986
            bzrlib.trace.log_exception()
 
1987
            return 2
1882
1988
 
1883
1989
 
1884
1990
if __name__ == '__main__':