~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-09 03:03:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050509030355-ad6ab558d1362959
- Don't give an error if the trace file can't be opened

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
 
19
 
import sys, os
 
19
import sys, os, time, os.path
 
20
from sets import Set
20
21
 
21
22
import bzrlib
22
23
from bzrlib.trace import mutter, note, log_error
23
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
24
 
from bzrlib.osutils import quotefn
25
 
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
 
24
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
25
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
26
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
27
from bzrlib.revision import Revision
 
28
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
26
29
     format_date
27
30
 
28
31
 
29
 
plugin_cmds = {}
30
 
 
31
 
 
32
 
def register_command(cmd):
33
 
    "Utility function to help register a command"
34
 
    global plugin_cmds
35
 
    k = cmd.__name__
36
 
    if k.startswith("cmd_"):
37
 
        k_unsquished = _unsquish_command_name(k)
38
 
    else:
39
 
        k_unsquished = k
40
 
    if not plugin_cmds.has_key(k_unsquished):
41
 
        plugin_cmds[k_unsquished] = cmd
42
 
    else:
43
 
        log_error('Two plugins defined the same command: %r' % k)
44
 
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
45
 
 
46
 
 
47
32
def _squish_command_name(cmd):
48
33
    return 'cmd_' + cmd.replace('-', '_')
49
34
 
52
37
    assert cmd.startswith("cmd_")
53
38
    return cmd[4:].replace('_','-')
54
39
 
55
 
def _parse_revision_str(revstr):
56
 
    """This handles a revision string -> revno. 
57
 
 
58
 
    There are several possibilities:
59
 
 
60
 
        '234'       -> 234
61
 
        '234:345'   -> [234, 345]
62
 
        ':234'      -> [None, 234]
63
 
        '234:'      -> [234, None]
64
 
 
65
 
    In the future we will also support:
66
 
        'uuid:blah-blah-blah'   -> ?
67
 
        'hash:blahblahblah'     -> ?
68
 
        potentially:
69
 
        'tag:mytag'             -> ?
70
 
    """
71
 
    if revstr.find(':') != -1:
72
 
        revs = revstr.split(':')
73
 
        if len(revs) > 2:
74
 
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
75
 
 
76
 
        if not revs[0]:
77
 
            revs[0] = None
78
 
        else:
79
 
            revs[0] = int(revs[0])
80
 
 
81
 
        if not revs[1]:
82
 
            revs[1] = None
83
 
        else:
84
 
            revs[1] = int(revs[1])
85
 
    else:
86
 
        revs = int(revstr)
87
 
    return revs
88
 
 
89
 
 
90
 
 
91
 
def _get_cmd_dict(plugins_override=True):
92
 
    d = {}
 
40
def get_all_cmds():
 
41
    """Return canonical name and class for all registered commands."""
93
42
    for k, v in globals().iteritems():
94
43
        if k.startswith("cmd_"):
95
 
            d[_unsquish_command_name(k)] = v
96
 
    # If we didn't load plugins, the plugin_cmds dict will be empty
97
 
    if plugins_override:
98
 
        d.update(plugin_cmds)
99
 
    else:
100
 
        d2 = plugin_cmds.copy()
101
 
        d2.update(d)
102
 
        d = d2
103
 
    return d
104
 
 
105
 
    
106
 
def get_all_cmds(plugins_override=True):
107
 
    """Return canonical name and class for all registered commands."""
108
 
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
109
 
        yield k,v
110
 
 
111
 
 
112
 
def get_cmd_class(cmd, plugins_override=True):
 
44
            yield _unsquish_command_name(k), v
 
45
 
 
46
def get_cmd_class(cmd):
113
47
    """Return the canonical name and command class for a command.
114
48
    """
115
49
    cmd = str(cmd)                      # not unicode
116
50
 
117
51
    # first look up this command under the specified name
118
 
    cmds = _get_cmd_dict(plugins_override=plugins_override)
119
52
    try:
120
 
        return cmd, cmds[cmd]
 
53
        return cmd, globals()[_squish_command_name(cmd)]
121
54
    except KeyError:
122
55
        pass
123
56
 
124
57
    # look for any command which claims this as an alias
125
 
    for cmdname, cmdclass in cmds.iteritems():
 
58
    for cmdname, cmdclass in get_all_cmds():
126
59
        if cmd in cmdclass.aliases:
127
60
            return cmdname, cmdclass
128
 
 
129
 
    cmdclass = ExternalCommand.find_command(cmd)
130
 
    if cmdclass:
131
 
        return cmd, cmdclass
132
 
 
133
 
    raise BzrCommandError("unknown command %r" % cmd)
134
 
 
135
 
 
136
 
class Command(object):
 
61
    else:
 
62
        raise BzrCommandError("unknown command %r" % cmd)
 
63
 
 
64
 
 
65
class Command:
137
66
    """Base class for commands.
138
67
 
139
68
    The docstring for an actual command should give a single-line
182
111
        return 0
183
112
 
184
113
 
185
 
class ExternalCommand(Command):
186
 
    """Class to wrap external commands.
187
 
 
188
 
    We cheat a little here, when get_cmd_class() calls us we actually give it back
189
 
    an object we construct that has the appropriate path, help, options etc for the
190
 
    specified command.
191
 
 
192
 
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
193
 
    method, which we override to call the Command.__init__ method. That then calls
194
 
    our run method which is pretty straight forward.
195
 
 
196
 
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
197
 
    back into command line options and arguments for the script.
198
 
    """
199
 
 
200
 
    def find_command(cls, cmd):
201
 
        import os.path
202
 
        bzrpath = os.environ.get('BZRPATH', '')
203
 
 
204
 
        for dir in bzrpath.split(os.pathsep):
205
 
            path = os.path.join(dir, cmd)
206
 
            if os.path.isfile(path):
207
 
                return ExternalCommand(path)
208
 
 
209
 
        return None
210
 
 
211
 
    find_command = classmethod(find_command)
212
 
 
213
 
    def __init__(self, path):
214
 
        self.path = path
215
 
 
216
 
        pipe = os.popen('%s --bzr-usage' % path, 'r')
217
 
        self.takes_options = pipe.readline().split()
218
 
 
219
 
        for opt in self.takes_options:
220
 
            if not opt in OPTIONS:
221
 
                raise BzrError("Unknown option '%s' returned by external command %s"
222
 
                               % (opt, path))
223
 
 
224
 
        # TODO: Is there any way to check takes_args is valid here?
225
 
        self.takes_args = pipe.readline().split()
226
 
 
227
 
        if pipe.close() is not None:
228
 
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
229
 
 
230
 
        pipe = os.popen('%s --bzr-help' % path, 'r')
231
 
        self.__doc__ = pipe.read()
232
 
        if pipe.close() is not None:
233
 
            raise BzrError("Failed funning '%s --bzr-help'" % path)
234
 
 
235
 
    def __call__(self, options, arguments):
236
 
        Command.__init__(self, options, arguments)
237
 
        return self
238
 
 
239
 
    def run(self, **kargs):
240
 
        opts = []
241
 
        args = []
242
 
 
243
 
        keys = kargs.keys()
244
 
        keys.sort()
245
 
        for name in keys:
246
 
            optname = name.replace('_','-')
247
 
            value = kargs[name]
248
 
            if OPTIONS.has_key(optname):
249
 
                # it's an option
250
 
                opts.append('--%s' % optname)
251
 
                if value is not None and value is not True:
252
 
                    opts.append(str(value))
253
 
            else:
254
 
                # it's an arg, or arg list
255
 
                if type(value) is not list:
256
 
                    value = [value]
257
 
                for v in value:
258
 
                    if v is not None:
259
 
                        args.append(str(v))
260
 
 
261
 
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
262
 
        return self.status
263
 
 
264
114
 
265
115
class cmd_status(Command):
266
116
    """Display status summary.
267
117
 
268
 
    This reports on versioned and unknown files, reporting them
269
 
    grouped by state.  Possible states are:
270
 
 
271
 
    added
272
 
        Versioned in the working copy but not in the previous revision.
273
 
 
274
 
    removed
275
 
        Versioned in the previous revision but removed or deleted
276
 
        in the working copy.
277
 
 
278
 
    renamed
279
 
        Path of this file changed from the previous revision;
280
 
        the text may also have changed.  This includes files whose
281
 
        parent directory was renamed.
282
 
 
283
 
    modified
284
 
        Text has changed since the previous revision.
285
 
 
286
 
    unchanged
287
 
        Nothing about this file has changed since the previous revision.
288
 
        Only shown with --all.
289
 
 
290
 
    unknown
291
 
        Not versioned and not matching an ignore pattern.
292
 
 
293
 
    To see ignored files use 'bzr ignored'.  For details in the
294
 
    changes to file texts, use 'bzr diff'.
295
 
 
296
 
    If no arguments are specified, the status of the entire working
297
 
    directory is shown.  Otherwise, only the status of the specified
298
 
    files or directories is reported.  If a directory is given, status
299
 
    is reported for everything inside that directory.
 
118
    For each file there is a single line giving its file state and name.
 
119
    The name is that in the current revision unless it is deleted or
 
120
    missing, in which case the old name is shown.
300
121
    """
301
 
    takes_args = ['file*']
302
 
    takes_options = ['all', 'show-ids']
 
122
    takes_options = ['all']
303
123
    aliases = ['st', 'stat']
304
124
    
305
 
    def run(self, all=False, show_ids=False, file_list=None):
306
 
        if file_list:
307
 
            b = Branch(file_list[0])
308
 
            file_list = [b.relpath(x) for x in file_list]
309
 
            # special case: only one path was given and it's the root
310
 
            # of the branch
311
 
            if file_list == ['']:
312
 
                file_list = None
313
 
        else:
314
 
            b = Branch('.')
315
 
        import status
316
 
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
317
 
                           specific_files=file_list)
 
125
    def run(self, all=False):
 
126
        #import bzrlib.status
 
127
        #bzrlib.status.tree_status(Branch('.'))
 
128
        Branch('.').show_status(show_all=all)
318
129
 
319
130
 
320
131
class cmd_cat_revision(Command):
357
168
    recursively add that parent, rather than giving an error?
358
169
    """
359
170
    takes_args = ['file+']
360
 
    takes_options = ['verbose', 'no-recurse']
 
171
    takes_options = ['verbose']
361
172
    
362
 
    def run(self, file_list, verbose=False, no_recurse=False):
363
 
        bzrlib.add.smart_add(file_list, verbose, not no_recurse)
364
 
 
365
 
 
366
 
 
367
 
class cmd_mkdir(Command):
368
 
    """Create a new versioned directory.
369
 
 
370
 
    This is equivalent to creating the directory and then adding it.
371
 
    """
372
 
    takes_args = ['dir+']
373
 
 
374
 
    def run(self, dir_list):
375
 
        import os
376
 
        import bzrlib.branch
377
 
        
378
 
        b = None
379
 
        
380
 
        for d in dir_list:
381
 
            os.mkdir(d)
382
 
            if not b:
383
 
                b = bzrlib.branch.Branch(d)
384
 
            b.add([d], verbose=True)
 
173
    def run(self, file_list, verbose=False):
 
174
        bzrlib.add.smart_add(file_list, verbose)
385
175
 
386
176
 
387
177
class cmd_relpath(Command):
388
178
    """Show path of a file relative to root"""
389
179
    takes_args = ['filename']
390
 
    hidden = True
391
180
    
392
181
    def run(self, filename):
393
182
        print Branch(filename).relpath(filename)
396
185
 
397
186
class cmd_inventory(Command):
398
187
    """Show inventory of the current working copy or a revision."""
399
 
    takes_options = ['revision', 'show-ids']
 
188
    takes_options = ['revision']
400
189
    
401
 
    def run(self, revision=None, show_ids=False):
 
190
    def run(self, revision=None):
402
191
        b = Branch('.')
403
192
        if revision == None:
404
193
            inv = b.read_working_inventory()
405
194
        else:
406
195
            inv = b.get_revision_inventory(b.lookup_revision(revision))
407
196
 
408
 
        for path, entry in inv.entries():
409
 
            if show_ids:
410
 
                print '%-50s %s' % (path, entry.file_id)
411
 
            else:
412
 
                print path
 
197
        for path, entry in inv.iter_entries():
 
198
            print '%-50s %s' % (entry.file_id, path)
413
199
 
414
200
 
415
201
class cmd_move(Command):
449
235
 
450
236
 
451
237
 
452
 
 
453
 
 
454
 
class cmd_pull(Command):
455
 
    """Pull any changes from another branch into the current one.
456
 
 
457
 
    If the location is omitted, the last-used location will be used.
458
 
    Both the revision history and the working directory will be
459
 
    updated.
460
 
 
461
 
    This command only works on branches that have not diverged.  Branches are
462
 
    considered diverged if both branches have had commits without first
463
 
    pulling from the other.
464
 
 
465
 
    If branches have diverged, you can use 'bzr merge' to pull the text changes
466
 
    from one into the other.
467
 
    """
468
 
    takes_args = ['location?']
469
 
 
470
 
    def run(self, location=None):
471
 
        from bzrlib.merge import merge
472
 
        import tempfile
473
 
        from shutil import rmtree
474
 
        import errno
475
 
        
476
 
        br_to = Branch('.')
477
 
        stored_loc = None
478
 
        try:
479
 
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
480
 
        except IOError, e:
481
 
            if e.errno != errno.ENOENT:
482
 
                raise
483
 
        if location is None:
484
 
            if stored_loc is None:
485
 
                raise BzrCommandError("No pull location known or specified.")
486
 
            else:
487
 
                print "Using last location: %s" % stored_loc
488
 
                location = stored_loc
489
 
        cache_root = tempfile.mkdtemp()
490
 
        try:
491
 
            from branch import find_cached_branch, DivergedBranches
492
 
            br_from = find_cached_branch(location, cache_root)
493
 
            location = pull_loc(br_from)
494
 
            old_revno = br_to.revno()
495
 
            try:
496
 
                br_to.update_revisions(br_from)
497
 
            except DivergedBranches:
498
 
                raise BzrCommandError("These branches have diverged."
499
 
                    "  Try merge.")
500
 
                
501
 
            merge(('.', -1), ('.', old_revno), check_clean=False)
502
 
            if location != stored_loc:
503
 
                br_to.controlfile("x-pull", "wb").write(location + "\n")
504
 
        finally:
505
 
            rmtree(cache_root)
506
 
 
507
 
 
508
 
 
509
 
class cmd_branch(Command):
510
 
    """Create a new copy of a branch.
511
 
 
512
 
    If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
513
 
    be used.  In other words, "branch ../foo/bar" will attempt to create ./bar.
514
 
 
515
 
    To retrieve the branch as of a particular revision, supply the --revision
516
 
    parameter, as in "branch foo/bar -r 5".
517
 
    """
518
 
    takes_args = ['from_location', 'to_location?']
519
 
    takes_options = ['revision']
520
 
 
521
 
    def run(self, from_location, to_location=None, revision=None):
522
 
        import errno
523
 
        from bzrlib.merge import merge
524
 
        from branch import find_cached_branch, DivergedBranches, NoSuchRevision
525
 
        from shutil import rmtree
526
 
        from meta_store import CachedStore
527
 
        import tempfile
528
 
        cache_root = tempfile.mkdtemp()
529
 
        try:
530
 
            try:
531
 
                br_from = find_cached_branch(from_location, cache_root)
532
 
            except OSError, e:
533
 
                if e.errno == errno.ENOENT:
534
 
                    raise BzrCommandError('Source location "%s" does not'
535
 
                                          ' exist.' % to_location)
536
 
                else:
537
 
                    raise
538
 
 
539
 
            if to_location is None:
540
 
                to_location = os.path.basename(from_location.rstrip("/\\"))
541
 
 
542
 
            try:
543
 
                os.mkdir(to_location)
544
 
            except OSError, e:
545
 
                if e.errno == errno.EEXIST:
546
 
                    raise BzrCommandError('Target directory "%s" already'
547
 
                                          ' exists.' % to_location)
548
 
                if e.errno == errno.ENOENT:
549
 
                    raise BzrCommandError('Parent of "%s" does not exist.' %
550
 
                                          to_location)
551
 
                else:
552
 
                    raise
553
 
            br_to = Branch(to_location, init=True)
554
 
 
555
 
            try:
556
 
                br_to.update_revisions(br_from, stop_revision=revision)
557
 
            except NoSuchRevision:
558
 
                rmtree(to_location)
559
 
                msg = "The branch %s has no revision %d." % (from_location,
560
 
                                                             revision)
561
 
                raise BzrCommandError(msg)
562
 
            merge((to_location, -1), (to_location, 0), this_dir=to_location,
563
 
                  check_clean=False, ignore_zero=True)
564
 
            from_location = pull_loc(br_from)
565
 
            br_to.controlfile("x-pull", "wb").write(from_location + "\n")
566
 
        finally:
567
 
            rmtree(cache_root)
568
 
 
569
 
 
570
 
def pull_loc(branch):
571
 
    # TODO: Should perhaps just make attribute be 'base' in
572
 
    # RemoteBranch and Branch?
573
 
    if hasattr(branch, "baseurl"):
574
 
        return branch.baseurl
575
 
    else:
576
 
        return branch.base
577
 
 
578
 
 
579
 
 
580
238
class cmd_renames(Command):
581
239
    """Show list of renamed files.
582
240
 
598
256
 
599
257
 
600
258
class cmd_info(Command):
601
 
    """Show statistical information about a branch."""
602
 
    takes_args = ['branch?']
603
 
    
604
 
    def run(self, branch=None):
 
259
    """Show statistical information for this branch"""
 
260
    def run(self):
605
261
        import info
606
 
 
607
 
        from branch import find_branch
608
 
        b = find_branch(branch)
609
 
        info.show_info(b)
 
262
        info.show_info(Branch('.'))        
610
263
 
611
264
 
612
265
class cmd_remove(Command):
636
289
        b = Branch(filename)
637
290
        i = b.inventory.path2id(b.relpath(filename))
638
291
        if i == None:
639
 
            raise BzrError("%r is not a versioned file" % filename)
 
292
            bailout("%r is not a versioned file" % filename)
640
293
        else:
641
294
            print i
642
295
 
653
306
        inv = b.inventory
654
307
        fid = inv.path2id(b.relpath(filename))
655
308
        if fid == None:
656
 
            raise BzrError("%r is not a versioned file" % filename)
 
309
            bailout("%r is not a versioned file" % filename)
657
310
        for fip in inv.get_idpath(fid):
658
311
            print fip
659
312
 
660
313
 
661
314
class cmd_revision_history(Command):
662
315
    """Display list of revision ids on this branch."""
663
 
    hidden = True
664
316
    def run(self):
665
317
        for patchid in Branch('.').revision_history():
666
318
            print patchid
718
370
    """
719
371
    
720
372
    takes_args = ['file*']
721
 
    takes_options = ['revision', 'diff-options']
722
 
    aliases = ['di', 'dif']
 
373
    takes_options = ['revision']
 
374
    aliases = ['di']
723
375
 
724
 
    def run(self, revision=None, file_list=None, diff_options=None):
 
376
    def run(self, revision=None, file_list=None):
725
377
        from bzrlib.diff import show_diff
726
 
        from bzrlib import find_branch
727
 
 
728
 
        if file_list:
729
 
            b = find_branch(file_list[0])
730
 
            file_list = [b.relpath(f) for f in file_list]
731
 
            if file_list == ['']:
732
 
                # just pointing to top-of-tree
733
 
                file_list = None
734
 
        else:
735
 
            b = Branch('.')
736
378
    
737
 
        show_diff(b, revision, specific_files=file_list,
738
 
                  external_diff_options=diff_options)
739
 
 
740
 
 
741
 
        
 
379
        show_diff(Branch('.'), revision, file_list)
742
380
 
743
381
 
744
382
class cmd_deleted(Command):
763
401
                else:
764
402
                    print path
765
403
 
766
 
 
767
 
class cmd_modified(Command):
768
 
    """List files modified in working tree."""
769
 
    hidden = True
770
 
    def run(self):
771
 
        import statcache
772
 
        b = Branch('.')
773
 
        inv = b.read_working_inventory()
774
 
        sc = statcache.update_cache(b, inv)
775
 
        basis = b.basis_tree()
776
 
        basis_inv = basis.inventory
777
 
        
778
 
        # We used to do this through iter_entries(), but that's slow
779
 
        # when most of the files are unmodified, as is usually the
780
 
        # case.  So instead we iterate by inventory entry, and only
781
 
        # calculate paths as necessary.
782
 
 
783
 
        for file_id in basis_inv:
784
 
            cacheentry = sc.get(file_id)
785
 
            if not cacheentry:                 # deleted
786
 
                continue
787
 
            ie = basis_inv[file_id]
788
 
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
789
 
                path = inv.id2path(file_id)
790
 
                print path
791
 
 
792
 
 
793
 
 
794
 
class cmd_added(Command):
795
 
    """List files added in working tree."""
796
 
    hidden = True
797
 
    def run(self):
798
 
        b = Branch('.')
799
 
        wt = b.working_tree()
800
 
        basis_inv = b.basis_tree().inventory
801
 
        inv = wt.inventory
802
 
        for file_id in inv:
803
 
            if file_id in basis_inv:
804
 
                continue
805
 
            path = inv.id2path(file_id)
806
 
            if not os.access(b.abspath(path), os.F_OK):
807
 
                continue
808
 
            print path
809
 
                
810
 
        
811
 
 
812
404
class cmd_root(Command):
813
405
    """Show the tree root directory.
814
406
 
817
409
    takes_args = ['filename?']
818
410
    def run(self, filename=None):
819
411
        """Print the branch root."""
820
 
        from branch import find_branch
821
 
        b = find_branch(filename)
822
 
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
 
412
        print bzrlib.branch.find_branch_root(filename)
 
413
 
823
414
 
824
415
 
825
416
class cmd_log(Command):
826
417
    """Show log of this branch.
827
418
 
828
 
    To request a range of logs, you can use the command -r begin:end
829
 
    -r revision requests a specific revision, -r :end or -r begin: are
830
 
    also valid.
 
419
    TODO: Option to limit range.
831
420
 
832
 
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
833
 
  
 
421
    TODO: Perhaps show most-recent first with an option for last.
834
422
    """
835
 
 
836
423
    takes_args = ['filename?']
837
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
838
 
    
839
 
    def run(self, filename=None, timezone='original',
840
 
            verbose=False,
841
 
            show_ids=False,
842
 
            forward=False,
843
 
            revision=None):
844
 
        from bzrlib import show_log, find_branch
845
 
        from bzrlib.log import log_formatter
846
 
        import codecs
847
 
 
848
 
        direction = (forward and 'forward') or 'reverse'
849
 
        
 
424
    takes_options = ['timezone', 'verbose', 'show-ids']
 
425
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
 
426
        b = Branch((filename or '.'), lock_mode='r')
850
427
        if filename:
851
 
            b = find_branch(filename)
852
 
            fp = b.relpath(filename)
853
 
            if fp:
854
 
                file_id = b.read_working_inventory().path2id(fp)
855
 
            else:
856
 
                file_id = None  # points to branch root
857
 
        else:
858
 
            b = find_branch('.')
859
 
            file_id = None
860
 
 
861
 
        if revision == None:
862
 
            revision = [None, None]
863
 
        elif isinstance(revision, int):
864
 
            revision = [revision, revision]
865
 
        else:
866
 
            # pair of revisions?
867
 
            pass
868
 
            
869
 
        assert len(revision) == 2
870
 
 
871
 
        mutter('encoding log as %r' % bzrlib.user_encoding)
872
 
 
873
 
        # use 'replace' so that we don't abort if trying to write out
874
 
        # in e.g. the default C locale.
875
 
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
876
 
 
877
 
        lf = log_formatter('short',
878
 
                           show_ids=show_ids,
879
 
                           to_file=outf,
880
 
                           show_timezone=timezone)
881
 
 
882
 
        show_log(b,
883
 
                 lf,
884
 
                 file_id,
885
 
                 verbose=verbose,
886
 
                 direction=direction,
887
 
                 start_revision=revision[0],
888
 
                 end_revision=revision[1])
 
428
            filename = b.relpath(filename)
 
429
        bzrlib.show_log(b, filename,
 
430
                        show_timezone=timezone,
 
431
                        verbose=verbose,
 
432
                        show_ids=show_ids)
889
433
 
890
434
 
891
435
 
892
436
class cmd_touching_revisions(Command):
893
 
    """Return revision-ids which affected a particular file.
894
 
 
895
 
    A more user-friendly interface is "bzr log FILE"."""
 
437
    """Return revision-ids which affected a particular file."""
896
438
    hidden = True
897
439
    takes_args = ["filename"]
898
440
    def run(self, filename):
899
 
        b = Branch(filename)
 
441
        b = Branch(filename, lock_mode='r')
900
442
        inv = b.read_working_inventory()
901
443
        file_id = inv.path2id(b.relpath(filename))
902
444
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
932
474
 
933
475
 
934
476
class cmd_unknowns(Command):
935
 
    """List unknown files."""
 
477
    """List unknown files"""
936
478
    def run(self):
937
479
        for f in Branch('.').unknowns():
938
480
            print quotefn(f)
940
482
 
941
483
 
942
484
class cmd_ignore(Command):
943
 
    """Ignore a command or pattern.
944
 
 
945
 
    To remove patterns from the ignore list, edit the .bzrignore file.
946
 
 
947
 
    If the pattern contains a slash, it is compared to the whole path
948
 
    from the branch root.  Otherwise, it is comapred to only the last
949
 
    component of the path.
950
 
 
951
 
    Ignore patterns are case-insensitive on case-insensitive systems.
952
 
 
953
 
    Note: wildcards must be quoted from the shell on Unix.
954
 
 
955
 
    examples:
956
 
        bzr ignore ./Makefile
957
 
        bzr ignore '*.class'
958
 
    """
 
485
    """Ignore a command or pattern"""
959
486
    takes_args = ['name_pattern']
960
487
    
961
488
    def run(self, name_pattern):
962
 
        from bzrlib.atomicfile import AtomicFile
963
 
        import os.path
964
 
 
965
489
        b = Branch('.')
966
 
        ifn = b.abspath('.bzrignore')
967
 
 
968
 
        if os.path.exists(ifn):
969
 
            f = open(ifn, 'rt')
970
 
            try:
971
 
                igns = f.read().decode('utf-8')
972
 
            finally:
973
 
                f.close()
974
 
        else:
975
 
            igns = ''
976
 
 
977
 
        # TODO: If the file already uses crlf-style termination, maybe
978
 
        # we should use that for the newly added lines?
979
 
 
980
 
        if igns and igns[-1] != '\n':
981
 
            igns += '\n'
982
 
        igns += name_pattern + '\n'
983
 
 
984
 
        try:
985
 
            f = AtomicFile(ifn, 'wt')
986
 
            f.write(igns.encode('utf-8'))
987
 
            f.commit()
988
 
        finally:
989
 
            f.close()
 
490
 
 
491
        # XXX: This will fail if it's a hardlink; should use an AtomicFile class.
 
492
        f = open(b.abspath('.bzrignore'), 'at')
 
493
        f.write(name_pattern + '\n')
 
494
        f.close()
990
495
 
991
496
        inv = b.working_tree().inventory
992
497
        if inv.path2id('.bzrignore'):
998
503
 
999
504
 
1000
505
class cmd_ignored(Command):
1001
 
    """List ignored files and the patterns that matched them.
1002
 
 
1003
 
    See also: bzr ignore"""
 
506
    """List ignored files and the patterns that matched them."""
1004
507
    def run(self):
1005
508
        tree = Branch('.').working_tree()
1006
509
        for path, file_class, kind, file_id in tree.list_files():
1016
519
 
1017
520
    example:
1018
521
        bzr lookup-revision 33
1019
 
    """
 
522
        """
1020
523
    hidden = True
1021
524
    takes_args = ['revno']
1022
525
    
1032
535
class cmd_export(Command):
1033
536
    """Export past revision to destination directory.
1034
537
 
1035
 
    If no revision is specified this exports the last committed revision.
1036
 
 
1037
 
    Format may be an "exporter" name, such as tar, tgz, tbz2.  If none is
1038
 
    given, exports to a directory (equivalent to --format=dir)."""
1039
 
    # TODO: list known exporters
 
538
    If no revision is specified this exports the last committed revision."""
1040
539
    takes_args = ['dest']
1041
 
    takes_options = ['revision', 'format']
1042
 
    def run(self, dest, revision=None, format='dir'):
 
540
    takes_options = ['revision']
 
541
    def run(self, dest, revision=None):
1043
542
        b = Branch('.')
1044
543
        if revision == None:
1045
544
            rh = b.revision_history()[-1]
1046
545
        else:
1047
546
            rh = b.lookup_revision(int(revision))
1048
547
        t = b.revision_tree(rh)
1049
 
        t.export(dest, format)
 
548
        t.export(dest)
1050
549
 
1051
550
 
1052
551
class cmd_cat(Command):
1073
572
class cmd_commit(Command):
1074
573
    """Commit changes into a new revision.
1075
574
 
1076
 
    If selected files are specified, only changes to those files are
1077
 
    committed.  If a directory is specified then its contents are also
1078
 
    committed.
1079
 
 
1080
 
    A selected-file commit may fail in some cases where the committed
1081
 
    tree would be invalid, such as trying to commit a file in a
1082
 
    newly-added directory that is not itself committed.
 
575
    TODO: Commit only selected files.
1083
576
 
1084
577
    TODO: Run hooks on tree to-be-committed, and after commit.
1085
578
 
1086
579
    TODO: Strict commit that fails if there are unknown or deleted files.
1087
580
    """
1088
 
    takes_args = ['selected*']
1089
581
    takes_options = ['message', 'file', 'verbose']
1090
582
    aliases = ['ci', 'checkin']
1091
583
 
1092
 
    def run(self, message=None, file=None, verbose=True, selected_list=None):
1093
 
        from bzrlib.commit import commit
1094
 
        from bzrlib.osutils import get_text_message
1095
 
 
 
584
    def run(self, message=None, file=None, verbose=False):
1096
585
        ## Warning: shadows builtin file()
1097
586
        if not message and not file:
1098
 
            import cStringIO
1099
 
            stdout = sys.stdout
1100
 
            catcher = cStringIO.StringIO()
1101
 
            sys.stdout = catcher
1102
 
            cmd_status({"file_list":selected_list}, {})
1103
 
            info = catcher.getvalue()
1104
 
            sys.stdout = stdout
1105
 
            message = get_text_message(info)
1106
 
            
1107
 
            if message is None:
1108
 
                raise BzrCommandError("please specify a commit message",
1109
 
                                      ["use either --message or --file"])
 
587
            raise BzrCommandError("please specify a commit message",
 
588
                                  ["use either --message or --file"])
1110
589
        elif message and file:
1111
590
            raise BzrCommandError("please specify either --message or --file")
1112
591
        
1114
593
            import codecs
1115
594
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1116
595
 
1117
 
        b = Branch('.')
1118
 
        commit(b, message, verbose=verbose, specific_files=selected_list)
 
596
        Branch('.').commit(message, verbose=verbose)
1119
597
 
1120
598
 
1121
599
class cmd_check(Command):
1123
601
 
1124
602
    This command checks various invariants about the branch storage to
1125
603
    detect data corruption or bzr bugs.
1126
 
 
1127
 
    If given the --update flag, it will update some optional fields
1128
 
    to help ensure data consistency.
1129
604
    """
1130
605
    takes_args = ['dir?']
1131
 
 
1132
606
    def run(self, dir='.'):
1133
607
        import bzrlib.check
1134
 
        bzrlib.check.check(Branch(dir))
1135
 
 
1136
 
 
1137
 
 
1138
 
class cmd_upgrade(Command):
1139
 
    """Upgrade branch storage to current format.
1140
 
 
1141
 
    This should normally be used only after the check command tells
1142
 
    you to run it.
1143
 
    """
1144
 
    takes_args = ['dir?']
1145
 
 
1146
 
    def run(self, dir='.'):
1147
 
        from bzrlib.upgrade import upgrade
1148
 
        upgrade(Branch(dir))
 
608
        bzrlib.check.check(Branch(dir, find_root=False))
1149
609
 
1150
610
 
1151
611
 
1164
624
    """Run internal test suite"""
1165
625
    hidden = True
1166
626
    def run(self):
1167
 
        from bzrlib.selftest import selftest
1168
 
        return int(not selftest())
 
627
        failures, tests = 0, 0
 
628
 
 
629
        import doctest, bzrlib.store, bzrlib.tests
 
630
        bzrlib.trace.verbose = False
 
631
 
 
632
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
633
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
634
            mf, mt = doctest.testmod(m)
 
635
            failures += mf
 
636
            tests += mt
 
637
            print '%-40s %3d tests' % (m.__name__, mt),
 
638
            if mf:
 
639
                print '%3d FAILED!' % mf
 
640
            else:
 
641
                print
 
642
 
 
643
        print '%-40s %3d tests' % ('total', tests),
 
644
        if failures:
 
645
            print '%3d FAILED!' % failures
 
646
        else:
 
647
            print
 
648
 
1169
649
 
1170
650
 
1171
651
class cmd_version(Command):
1172
 
    """Show version of bzr."""
 
652
    """Show version of bzr"""
1173
653
    def run(self):
1174
654
        show_version()
1175
655
 
1176
656
def show_version():
1177
657
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
1178
 
    # is bzrlib itself in a branch?
1179
 
    bzrrev = bzrlib.get_bzr_revision()
1180
 
    if bzrrev:
1181
 
        print "  (bzr checkout, revision %d {%s})" % bzrrev
1182
658
    print bzrlib.__copyright__
1183
659
    print "http://bazaar-ng.org/"
1184
660
    print
1193
669
    def run(self):
1194
670
        print "it sure does!"
1195
671
 
1196
 
def parse_spec(spec):
1197
 
    """
1198
 
    >>> parse_spec(None)
1199
 
    [None, None]
1200
 
    >>> parse_spec("./")
1201
 
    ['./', None]
1202
 
    >>> parse_spec("../@")
1203
 
    ['..', -1]
1204
 
    >>> parse_spec("../f/@35")
1205
 
    ['../f', 35]
1206
 
    """
1207
 
    if spec is None:
1208
 
        return [None, None]
1209
 
    if '/@' in spec:
1210
 
        parsed = spec.split('/@')
1211
 
        assert len(parsed) == 2
1212
 
        if parsed[1] == "":
1213
 
            parsed[1] = -1
1214
 
        else:
1215
 
            parsed[1] = int(parsed[1])
1216
 
            assert parsed[1] >=0
1217
 
    else:
1218
 
        parsed = [spec, None]
1219
 
    return parsed
1220
 
 
1221
 
 
1222
 
 
1223
 
class cmd_merge(Command):
1224
 
    """Perform a three-way merge of trees.
1225
 
    
1226
 
    The SPEC parameters are working tree or revision specifiers.  Working trees
1227
 
    are specified using standard paths or urls.  No component of a directory
1228
 
    path may begin with '@'.
1229
 
    
1230
 
    Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
1231
 
 
1232
 
    Revisions are specified using a dirname/@revno pair, where dirname is the
1233
 
    branch directory and revno is the revision within that branch.  If no revno
1234
 
    is specified, the latest revision is used.
1235
 
 
1236
 
    Revision examples: './@127', 'foo/@', '../@1'
1237
 
 
1238
 
    The OTHER_SPEC parameter is required.  If the BASE_SPEC parameter is
1239
 
    not supplied, the common ancestor of OTHER_SPEC the current branch is used
1240
 
    as the BASE.
1241
 
 
1242
 
    merge refuses to run if there are any uncommitted changes, unless
1243
 
    --force is given.
1244
 
    """
1245
 
    takes_args = ['other_spec', 'base_spec?']
1246
 
    takes_options = ['force']
1247
 
 
1248
 
    def run(self, other_spec, base_spec=None, force=False):
1249
 
        from bzrlib.merge import merge
1250
 
        merge(parse_spec(other_spec), parse_spec(base_spec),
1251
 
              check_clean=(not force))
1252
 
 
1253
 
 
1254
 
 
1255
 
class cmd_revert(Command):
1256
 
    """Restore selected files from a previous revision.
1257
 
    """
1258
 
    takes_args = ['file+']
1259
 
    def run(self, file_list):
1260
 
        from bzrlib.branch import find_branch
1261
 
        
1262
 
        if not file_list:
1263
 
            file_list = ['.']
1264
 
            
1265
 
        b = find_branch(file_list[0])
1266
 
 
1267
 
        b.revert([b.relpath(f) for f in file_list])
1268
 
 
1269
 
 
1270
 
class cmd_merge_revert(Command):
1271
 
    """Reverse all changes since the last commit.
1272
 
 
1273
 
    Only versioned files are affected.
1274
 
 
1275
 
    TODO: Store backups of any files that will be reverted, so
1276
 
          that the revert can be undone.          
1277
 
    """
1278
 
    takes_options = ['revision']
1279
 
 
1280
 
    def run(self, revision=-1):
1281
 
        from bzrlib.merge import merge
1282
 
        merge(('.', revision), parse_spec('.'),
1283
 
              check_clean=False,
1284
 
              ignore_zero=True)
1285
 
 
1286
672
 
1287
673
class cmd_assert_fail(Command):
1288
674
    """Test reporting of assertion failures"""
1303
689
        help.help(topic)
1304
690
 
1305
691
 
1306
 
class cmd_update_stat_cache(Command):
1307
 
    """Update stat-cache mapping inodes to SHA-1 hashes.
1308
 
 
1309
 
    For testing only."""
1310
 
    hidden = True
1311
 
    def run(self):
1312
 
        import statcache
1313
 
        b = Branch('.')
1314
 
        statcache.update_cache(b.base, b.read_working_inventory())
1315
 
 
1316
 
 
1317
 
 
1318
 
class cmd_plugins(Command):
1319
 
    """List plugins"""
1320
 
    hidden = True
1321
 
    def run(self):
1322
 
        import bzrlib.plugin
1323
 
        from pprint import pprint
1324
 
        pprint(bzrlib.plugin.all_plugins)
1325
 
 
 
692
######################################################################
 
693
# main routine
1326
694
 
1327
695
 
1328
696
# list of all available options; the rhs can be either None for an
1330
698
# the type.
1331
699
OPTIONS = {
1332
700
    'all':                    None,
1333
 
    'diff-options':           str,
1334
701
    'help':                   None,
1335
702
    'file':                   unicode,
1336
 
    'force':                  None,
1337
 
    'format':                 unicode,
1338
 
    'forward':                None,
1339
703
    'message':                unicode,
1340
 
    'no-recurse':             None,
1341
704
    'profile':                None,
1342
 
    'revision':               _parse_revision_str,
 
705
    'revision':               int,
1343
706
    'show-ids':               None,
1344
707
    'timezone':               str,
1345
708
    'verbose':                None,
1346
709
    'version':                None,
1347
710
    'email':                  None,
1348
 
    'update':                 None,
1349
711
    }
1350
712
 
1351
713
SHORT_OPTIONS = {
 
714
    'm':                      'message',
1352
715
    'F':                      'file', 
1353
 
    'h':                      'help',
1354
 
    'm':                      'message',
1355
716
    'r':                      'revision',
1356
717
    'v':                      'verbose',
1357
718
}
1373
734
    (['status'], {'all': True})
1374
735
    >>> parse_args('commit --message=biter'.split())
1375
736
    (['commit'], {'message': u'biter'})
1376
 
    >>> parse_args('log -r 500'.split())
1377
 
    (['log'], {'revision': 500})
1378
 
    >>> parse_args('log -r500:600'.split())
1379
 
    (['log'], {'revision': [500, 600]})
1380
 
    >>> parse_args('log -vr500:600'.split())
1381
 
    (['log'], {'verbose': True, 'revision': [500, 600]})
1382
 
    >>> parse_args('log -rv500:600'.split()) #the r takes an argument
1383
 
    Traceback (most recent call last):
1384
 
    ...
1385
 
    ValueError: invalid literal for int(): v500
1386
737
    """
1387
738
    args = []
1388
739
    opts = {}
1402
753
                else:
1403
754
                    optname = a[2:]
1404
755
                if optname not in OPTIONS:
1405
 
                    raise BzrError('unknown long option %r' % a)
 
756
                    bailout('unknown long option %r' % a)
1406
757
            else:
1407
758
                shortopt = a[1:]
1408
 
                if shortopt in SHORT_OPTIONS:
1409
 
                    # Multi-character options must have a space to delimit
1410
 
                    # their value
1411
 
                    optname = SHORT_OPTIONS[shortopt]
1412
 
                else:
1413
 
                    # Single character short options, can be chained,
1414
 
                    # and have their value appended to their name
1415
 
                    shortopt = a[1:2]
1416
 
                    if shortopt not in SHORT_OPTIONS:
1417
 
                        # We didn't find the multi-character name, and we
1418
 
                        # didn't find the single char name
1419
 
                        raise BzrError('unknown short option %r' % a)
1420
 
                    optname = SHORT_OPTIONS[shortopt]
1421
 
 
1422
 
                    if a[2:]:
1423
 
                        # There are extra things on this option
1424
 
                        # see if it is the value, or if it is another
1425
 
                        # short option
1426
 
                        optargfn = OPTIONS[optname]
1427
 
                        if optargfn is None:
1428
 
                            # This option does not take an argument, so the
1429
 
                            # next entry is another short option, pack it back
1430
 
                            # into the list
1431
 
                            argv.insert(0, '-' + a[2:])
1432
 
                        else:
1433
 
                            # This option takes an argument, so pack it
1434
 
                            # into the array
1435
 
                            optarg = a[2:]
 
759
                if shortopt not in SHORT_OPTIONS:
 
760
                    bailout('unknown short option %r' % a)
 
761
                optname = SHORT_OPTIONS[shortopt]
1436
762
            
1437
763
            if optname in opts:
1438
764
                # XXX: Do we ever want to support this, e.g. for -r?
1439
 
                raise BzrError('repeated option %r' % a)
 
765
                bailout('repeated option %r' % a)
1440
766
                
1441
767
            optargfn = OPTIONS[optname]
1442
768
            if optargfn:
1443
769
                if optarg == None:
1444
770
                    if not argv:
1445
 
                        raise BzrError('option %r needs an argument' % a)
 
771
                        bailout('option %r needs an argument' % a)
1446
772
                    else:
1447
773
                        optarg = argv.pop(0)
1448
774
                opts[optname] = optargfn(optarg)
1449
775
            else:
1450
776
                if optarg != None:
1451
 
                    raise BzrError('option %r takes no argument' % optname)
 
777
                    bailout('option %r takes no argument' % optname)
1452
778
                opts[optname] = True
1453
779
        else:
1454
780
            args.append(a)
1502
828
    return argdict
1503
829
 
1504
830
 
1505
 
def _parse_master_args(argv):
1506
 
    """Parse the arguments that always go with the original command.
1507
 
    These are things like bzr --no-plugins, etc.
1508
 
 
1509
 
    There are now 2 types of option flags. Ones that come *before* the command,
1510
 
    and ones that come *after* the command.
1511
 
    Ones coming *before* the command are applied against all possible commands.
1512
 
    And are generally applied before plugins are loaded.
1513
 
 
1514
 
    The current list are:
1515
 
        --builtin   Allow plugins to load, but don't let them override builtin commands,
1516
 
                    they will still be allowed if they do not override a builtin.
1517
 
        --no-plugins    Don't load any plugins. This lets you get back to official source
1518
 
                        behavior.
1519
 
        --profile   Enable the hotspot profile before running the command.
1520
 
                    For backwards compatibility, this is also a non-master option.
1521
 
        --version   Spit out the version of bzr that is running and exit.
1522
 
                    This is also a non-master option.
1523
 
        --help      Run help and exit, also a non-master option (I think that should stay, though)
1524
 
 
1525
 
    >>> argv, opts = _parse_master_args(['bzr', '--test'])
1526
 
    Traceback (most recent call last):
1527
 
    ...
1528
 
    BzrCommandError: Invalid master option: 'test'
1529
 
    >>> argv, opts = _parse_master_args(['bzr', '--version', 'command'])
1530
 
    >>> print argv
1531
 
    ['command']
1532
 
    >>> print opts['version']
1533
 
    True
1534
 
    >>> argv, opts = _parse_master_args(['bzr', '--profile', 'command', '--more-options'])
1535
 
    >>> print argv
1536
 
    ['command', '--more-options']
1537
 
    >>> print opts['profile']
1538
 
    True
1539
 
    >>> argv, opts = _parse_master_args(['bzr', '--no-plugins', 'command'])
1540
 
    >>> print argv
1541
 
    ['command']
1542
 
    >>> print opts['no-plugins']
1543
 
    True
1544
 
    >>> print opts['profile']
1545
 
    False
1546
 
    >>> argv, opts = _parse_master_args(['bzr', 'command', '--profile'])
1547
 
    >>> print argv
1548
 
    ['command', '--profile']
1549
 
    >>> print opts['profile']
1550
 
    False
1551
 
    """
1552
 
    master_opts = {'builtin':False,
1553
 
        'no-plugins':False,
1554
 
        'version':False,
1555
 
        'profile':False,
1556
 
        'help':False
1557
 
    }
1558
 
 
1559
 
    # This is the point where we could hook into argv[0] to determine
1560
 
    # what front-end is supposed to be run
1561
 
    # For now, we are just ignoring it.
1562
 
    cmd_name = argv.pop(0)
1563
 
    for arg in argv[:]:
1564
 
        if arg[:2] != '--': # at the first non-option, we return the rest
1565
 
            break
1566
 
        arg = arg[2:] # Remove '--'
1567
 
        if arg not in master_opts:
1568
 
            # We could say that this is not an error, that we should
1569
 
            # just let it be handled by the main section instead
1570
 
            raise BzrCommandError('Invalid master option: %r' % arg)
1571
 
        argv.pop(0) # We are consuming this entry
1572
 
        master_opts[arg] = True
1573
 
    return argv, master_opts
1574
 
 
1575
 
 
1576
831
 
1577
832
def run_bzr(argv):
1578
833
    """Execute a command.
1583
838
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
1584
839
    
1585
840
    try:
1586
 
        # some options like --builtin and --no-plugins have special effects
1587
 
        argv, master_opts = _parse_master_args(argv)
1588
 
        if not master_opts['no-plugins']:
1589
 
            bzrlib.load_plugins()
1590
 
 
1591
 
        args, opts = parse_args(argv)
1592
 
 
1593
 
        if master_opts['help']:
1594
 
            from bzrlib.help import help
1595
 
            if argv:
1596
 
                help(argv[0])
1597
 
            else:
1598
 
                help()
1599
 
            return 0            
1600
 
            
 
841
        args, opts = parse_args(argv[1:])
1601
842
        if 'help' in opts:
1602
 
            from bzrlib.help import help
 
843
            import help
1603
844
            if args:
1604
 
                help(args[0])
 
845
                help.help(args[0])
1605
846
            else:
1606
 
                help()
 
847
                help.help()
1607
848
            return 0
1608
849
        elif 'version' in opts:
1609
850
            show_version()
1610
851
            return 0
1611
 
        elif args and args[0] == 'builtin':
1612
 
            include_plugins=False
1613
 
            args = args[1:]
1614
852
        cmd = str(args.pop(0))
1615
853
    except IndexError:
1616
 
        import help
1617
 
        help.help()
 
854
        log_error('usage: bzr COMMAND')
 
855
        log_error('  try "bzr help"')
1618
856
        return 1
1619
 
          
1620
 
 
1621
 
    plugins_override = not (master_opts['builtin'])
1622
 
    canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
1623
 
 
1624
 
    profile = master_opts['profile']
1625
 
    # For backwards compatibility, I would rather stick with --profile being a
1626
 
    # master/global option
 
857
 
 
858
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
859
 
 
860
    # global option
1627
861
    if 'profile' in opts:
1628
862
        profile = True
1629
863
        del opts['profile']
 
864
    else:
 
865
        profile = False
1630
866
 
1631
867
    # check options are reasonable
1632
868
    allowed = cmd_class.takes_options
1663
899
            os.close(pffileno)
1664
900
            os.remove(pfname)
1665
901
    else:
1666
 
        return cmd_class(cmdopts, cmdargs).status 
 
902
        cmdobj = cmd_class(cmdopts, cmdargs).status 
1667
903
 
1668
904
 
1669
905
def _report_exception(summary, quiet=False):
1711
947
            return 2
1712
948
        except Exception, e:
1713
949
            quiet = False
1714
 
            if (isinstance(e, IOError) 
1715
 
                and hasattr(e, 'errno')
1716
 
                and e.errno == errno.EPIPE):
 
950
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
1717
951
                quiet = True
1718
952
                msg = 'broken pipe'
1719
953
            else: