~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Aaron Bentley
  • Date: 2005-07-29 17:19:16 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 1020.
  • Revision ID: abentley@panoramicfeedback.com-20050729171916-322fd81b451d2e3e
Added merge-type parameter to merge.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import sys, os
20
20
 
21
21
import bzrlib
22
 
from bzrlib.trace import mutter, note, log_error
23
 
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
24
 
from bzrlib.osutils import quotefn
25
 
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
26
 
     format_date
 
22
from bzrlib.trace import mutter, note, log_error, warning
 
23
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
 
24
from bzrlib.branch import find_branch
 
25
from bzrlib import BZRDIR
 
26
 
 
27
 
 
28
plugin_cmds = {}
 
29
 
 
30
 
 
31
def register_command(cmd):
 
32
    "Utility function to help register a command"
 
33
    global plugin_cmds
 
34
    k = cmd.__name__
 
35
    if k.startswith("cmd_"):
 
36
        k_unsquished = _unsquish_command_name(k)
 
37
    else:
 
38
        k_unsquished = k
 
39
    if not plugin_cmds.has_key(k_unsquished):
 
40
        plugin_cmds[k_unsquished] = cmd
 
41
    else:
 
42
        log_error('Two plugins defined the same command: %r' % k)
 
43
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
27
44
 
28
45
 
29
46
def _squish_command_name(cmd):
34
51
    assert cmd.startswith("cmd_")
35
52
    return cmd[4:].replace('_','-')
36
53
 
 
54
 
37
55
def _parse_revision_str(revstr):
38
 
    """This handles a revision string -> revno. 
39
 
 
40
 
    There are several possibilities:
41
 
 
42
 
        '234'       -> 234
43
 
        '234:345'   -> [234, 345]
44
 
        ':234'      -> [None, 234]
45
 
        '234:'      -> [234, None]
46
 
 
47
 
    In the future we will also support:
48
 
        'uuid:blah-blah-blah'   -> ?
49
 
        'hash:blahblahblah'     -> ?
50
 
        potentially:
51
 
        'tag:mytag'             -> ?
 
56
    """This handles a revision string -> revno.
 
57
 
 
58
    This always returns a list.  The list will have one element for 
 
59
 
 
60
    It supports integers directly, but everything else it
 
61
    defers for passing to Branch.get_revision_info()
 
62
 
 
63
    >>> _parse_revision_str('234')
 
64
    [234]
 
65
    >>> _parse_revision_str('234..567')
 
66
    [234, 567]
 
67
    >>> _parse_revision_str('..')
 
68
    [None, None]
 
69
    >>> _parse_revision_str('..234')
 
70
    [None, 234]
 
71
    >>> _parse_revision_str('234..')
 
72
    [234, None]
 
73
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
 
74
    [234, 456, 789]
 
75
    >>> _parse_revision_str('234....789') # Error?
 
76
    [234, None, 789]
 
77
    >>> _parse_revision_str('revid:test@other.com-234234')
 
78
    ['revid:test@other.com-234234']
 
79
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
 
80
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
 
81
    >>> _parse_revision_str('revid:test@other.com-234234..23')
 
82
    ['revid:test@other.com-234234', 23]
 
83
    >>> _parse_revision_str('date:2005-04-12')
 
84
    ['date:2005-04-12']
 
85
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
 
86
    ['date:2005-04-12 12:24:33']
 
87
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
 
88
    ['date:2005-04-12T12:24:33']
 
89
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
 
90
    ['date:2005-04-12,12:24:33']
 
91
    >>> _parse_revision_str('-5..23')
 
92
    [-5, 23]
 
93
    >>> _parse_revision_str('-5')
 
94
    [-5]
 
95
    >>> _parse_revision_str('123a')
 
96
    ['123a']
 
97
    >>> _parse_revision_str('abc')
 
98
    ['abc']
52
99
    """
53
 
    if revstr.find(':') != -1:
54
 
        revs = revstr.split(':')
55
 
        if len(revs) > 2:
56
 
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
57
 
 
58
 
        if not revs[0]:
59
 
            revs[0] = None
60
 
        else:
61
 
            revs[0] = int(revs[0])
62
 
 
63
 
        if not revs[1]:
64
 
            revs[1] = None
65
 
        else:
66
 
            revs[1] = int(revs[1])
67
 
    else:
68
 
        revs = int(revstr)
 
100
    import re
 
101
    old_format_re = re.compile('\d*:\d*')
 
102
    m = old_format_re.match(revstr)
 
103
    if m:
 
104
        warning('Colon separator for revision numbers is deprecated.'
 
105
                ' Use .. instead')
 
106
        revs = []
 
107
        for rev in revstr.split(':'):
 
108
            if rev:
 
109
                revs.append(int(rev))
 
110
            else:
 
111
                revs.append(None)
 
112
        return revs
 
113
    revs = []
 
114
    for x in revstr.split('..'):
 
115
        if not x:
 
116
            revs.append(None)
 
117
        else:
 
118
            try:
 
119
                revs.append(int(x))
 
120
            except ValueError:
 
121
                revs.append(x)
69
122
    return revs
70
123
 
71
 
def get_all_cmds():
72
 
    """Return canonical name and class for all registered commands."""
 
124
 
 
125
def get_merge_type(typestring):
 
126
    """Attempt to find the merge class/factory associated with a string."""
 
127
    from merge import merge_types
 
128
    try:
 
129
        return merge_types[typestring][0]
 
130
    except KeyError:
 
131
        templ = '%s%%7s: %%s' % (' '*12)
 
132
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
133
        type_list = '\n'.join(lines)
 
134
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
135
            (typestring, type_list)
 
136
        raise BzrCommandError(msg)
 
137
    
 
138
 
 
139
 
 
140
def _get_cmd_dict(plugins_override=True):
 
141
    d = {}
73
142
    for k, v in globals().iteritems():
74
143
        if k.startswith("cmd_"):
75
 
            yield _unsquish_command_name(k), v
76
 
 
77
 
def get_cmd_class(cmd):
 
144
            d[_unsquish_command_name(k)] = v
 
145
    # If we didn't load plugins, the plugin_cmds dict will be empty
 
146
    if plugins_override:
 
147
        d.update(plugin_cmds)
 
148
    else:
 
149
        d2 = plugin_cmds.copy()
 
150
        d2.update(d)
 
151
        d = d2
 
152
    return d
 
153
 
 
154
    
 
155
def get_all_cmds(plugins_override=True):
 
156
    """Return canonical name and class for all registered commands."""
 
157
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
158
        yield k,v
 
159
 
 
160
 
 
161
def get_cmd_class(cmd, plugins_override=True):
78
162
    """Return the canonical name and command class for a command.
79
163
    """
80
164
    cmd = str(cmd)                      # not unicode
81
165
 
82
166
    # first look up this command under the specified name
 
167
    cmds = _get_cmd_dict(plugins_override=plugins_override)
83
168
    try:
84
 
        return cmd, globals()[_squish_command_name(cmd)]
 
169
        return cmd, cmds[cmd]
85
170
    except KeyError:
86
171
        pass
87
172
 
88
173
    # look for any command which claims this as an alias
89
 
    for cmdname, cmdclass in get_all_cmds():
 
174
    for cmdname, cmdclass in cmds.iteritems():
90
175
        if cmd in cmdclass.aliases:
91
176
            return cmdname, cmdclass
92
177
 
129
214
        assert isinstance(arguments, dict)
130
215
        cmdargs = options.copy()
131
216
        cmdargs.update(arguments)
132
 
        assert self.__doc__ != Command.__doc__, \
133
 
               ("No help message set for %r" % self)
 
217
        if self.__doc__ == Command.__doc__:
 
218
            from warnings import warn
 
219
            warn("No help message set for %r" % self)
134
220
        self.status = self.run(**cmdargs)
 
221
        if self.status is None:
 
222
            self.status = 0
135
223
 
136
224
    
137
225
    def run(self):
165
253
        import os.path
166
254
        bzrpath = os.environ.get('BZRPATH', '')
167
255
 
168
 
        for dir in bzrpath.split(':'):
 
256
        for dir in bzrpath.split(os.pathsep):
169
257
            path = os.path.join(dir, cmd)
170
258
            if os.path.isfile(path):
171
259
                return ExternalCommand(path)
177
265
    def __init__(self, path):
178
266
        self.path = path
179
267
 
180
 
        # TODO: If either of these fail, we should detect that and
181
 
        # assume that path is not really a bzr plugin after all.
182
 
 
183
268
        pipe = os.popen('%s --bzr-usage' % path, 'r')
184
269
        self.takes_options = pipe.readline().split()
 
270
 
 
271
        for opt in self.takes_options:
 
272
            if not opt in OPTIONS:
 
273
                raise BzrError("Unknown option '%s' returned by external command %s"
 
274
                               % (opt, path))
 
275
 
 
276
        # TODO: Is there any way to check takes_args is valid here?
185
277
        self.takes_args = pipe.readline().split()
186
 
        pipe.close()
 
278
 
 
279
        if pipe.close() is not None:
 
280
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
187
281
 
188
282
        pipe = os.popen('%s --bzr-help' % path, 'r')
189
283
        self.__doc__ = pipe.read()
190
 
        pipe.close()
 
284
        if pipe.close() is not None:
 
285
            raise BzrError("Failed funning '%s --bzr-help'" % path)
191
286
 
192
287
    def __call__(self, options, arguments):
193
288
        Command.__init__(self, options, arguments)
200
295
        keys = kargs.keys()
201
296
        keys.sort()
202
297
        for name in keys:
 
298
            optname = name.replace('_','-')
203
299
            value = kargs[name]
204
 
            if OPTIONS.has_key(name):
 
300
            if OPTIONS.has_key(optname):
205
301
                # it's an option
206
 
                opts.append('--%s' % name)
 
302
                opts.append('--%s' % optname)
207
303
                if value is not None and value is not True:
208
304
                    opts.append(str(value))
209
305
            else:
253
349
    directory is shown.  Otherwise, only the status of the specified
254
350
    files or directories is reported.  If a directory is given, status
255
351
    is reported for everything inside that directory.
 
352
 
 
353
    If a revision is specified, the changes since that revision are shown.
256
354
    """
257
355
    takes_args = ['file*']
258
 
    takes_options = ['all', 'show-ids']
 
356
    takes_options = ['all', 'show-ids', 'revision']
259
357
    aliases = ['st', 'stat']
260
358
    
261
359
    def run(self, all=False, show_ids=False, file_list=None):
262
360
        if file_list:
263
 
            b = Branch(file_list[0])
 
361
            b = find_branch(file_list[0])
264
362
            file_list = [b.relpath(x) for x in file_list]
265
363
            # special case: only one path was given and it's the root
266
364
            # of the branch
267
365
            if file_list == ['']:
268
366
                file_list = None
269
367
        else:
270
 
            b = Branch('.')
271
 
        import status
272
 
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
273
 
                           specific_files=file_list)
 
368
            b = find_branch('.')
 
369
            
 
370
        from bzrlib.status import show_status
 
371
        show_status(b, show_unchanged=all, show_ids=show_ids,
 
372
                    specific_files=file_list)
274
373
 
275
374
 
276
375
class cmd_cat_revision(Command):
280
379
    takes_args = ['revision_id']
281
380
    
282
381
    def run(self, revision_id):
283
 
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
382
        from bzrlib.xml import pack_xml
 
383
        pack_xml(find_branch('.').get_revision(revision_id), sys.stdout)
284
384
 
285
385
 
286
386
class cmd_revno(Command):
288
388
 
289
389
    This is equal to the number of revisions on this branch."""
290
390
    def run(self):
291
 
        print Branch('.').revno()
 
391
        print find_branch('.').revno()
 
392
 
 
393
class cmd_revision_info(Command):
 
394
    """Show revision number and revision id for a given revision identifier.
 
395
    """
 
396
    hidden = True
 
397
    takes_args = ['revision_info*']
 
398
    takes_options = ['revision']
 
399
    def run(self, revision=None, revision_info_list=None):
 
400
        from bzrlib.branch import find_branch
 
401
 
 
402
        revs = []
 
403
        if revision is not None:
 
404
            revs.extend(revision)
 
405
        if revision_info_list is not None:
 
406
            revs.extend(revision_info_list)
 
407
        if len(revs) == 0:
 
408
            raise BzrCommandError('You must supply a revision identifier')
 
409
 
 
410
        b = find_branch('.')
 
411
 
 
412
        for rev in revs:
 
413
            print '%4d %s' % b.get_revision_info(rev)
292
414
 
293
415
    
294
416
class cmd_add(Command):
304
426
    whether already versioned or not, are searched for files or
305
427
    subdirectories that are neither versioned or ignored, and these
306
428
    are added.  This search proceeds recursively into versioned
307
 
    directories.
 
429
    directories.  If no names are given '.' is assumed.
308
430
 
309
 
    Therefore simply saying 'bzr add .' will version all files that
 
431
    Therefore simply saying 'bzr add' will version all files that
310
432
    are currently unknown.
311
433
 
312
434
    TODO: Perhaps adding a file whose directly is not versioned should
313
435
    recursively add that parent, rather than giving an error?
314
436
    """
315
 
    takes_args = ['file+']
 
437
    takes_args = ['file*']
316
438
    takes_options = ['verbose', 'no-recurse']
317
439
    
318
440
    def run(self, file_list, verbose=False, no_recurse=False):
319
 
        bzrlib.add.smart_add(file_list, verbose, not no_recurse)
 
441
        from bzrlib.add import smart_add
 
442
        smart_add(file_list, verbose, not no_recurse)
 
443
 
 
444
 
 
445
 
 
446
class cmd_mkdir(Command):
 
447
    """Create a new versioned directory.
 
448
 
 
449
    This is equivalent to creating the directory and then adding it.
 
450
    """
 
451
    takes_args = ['dir+']
 
452
 
 
453
    def run(self, dir_list):
 
454
        b = None
 
455
        
 
456
        for d in dir_list:
 
457
            os.mkdir(d)
 
458
            if not b:
 
459
                b = find_branch(d)
 
460
            b.add([d], verbose=True)
320
461
 
321
462
 
322
463
class cmd_relpath(Command):
325
466
    hidden = True
326
467
    
327
468
    def run(self, filename):
328
 
        print Branch(filename).relpath(filename)
 
469
        print find_branch(filename).relpath(filename)
329
470
 
330
471
 
331
472
 
334
475
    takes_options = ['revision', 'show-ids']
335
476
    
336
477
    def run(self, revision=None, show_ids=False):
337
 
        b = Branch('.')
 
478
        b = find_branch('.')
338
479
        if revision == None:
339
480
            inv = b.read_working_inventory()
340
481
        else:
341
 
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
482
            if len(revision) > 1:
 
483
                raise BzrCommandError('bzr inventory --revision takes'
 
484
                    ' exactly one revision identifier')
 
485
            inv = b.get_revision_inventory(b.lookup_revision(revision[0]))
342
486
 
343
487
        for path, entry in inv.entries():
344
488
            if show_ids:
357
501
    """
358
502
    takes_args = ['source$', 'dest']
359
503
    def run(self, source_list, dest):
360
 
        b = Branch('.')
 
504
        b = find_branch('.')
361
505
 
362
506
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
363
507
 
379
523
    takes_args = ['from_name', 'to_name']
380
524
    
381
525
    def run(self, from_name, to_name):
382
 
        b = Branch('.')
 
526
        b = find_branch('.')
383
527
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
384
528
 
385
529
 
404
548
 
405
549
    def run(self, location=None):
406
550
        from bzrlib.merge import merge
 
551
        import tempfile
 
552
        from shutil import rmtree
407
553
        import errno
408
554
        
409
 
        br_to = Branch('.')
 
555
        br_to = find_branch('.')
410
556
        stored_loc = None
411
557
        try:
412
558
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
413
559
        except IOError, e:
414
 
            if errno == errno.ENOENT:
 
560
            if e.errno != errno.ENOENT:
415
561
                raise
416
562
        if location is None:
417
 
            location = stored_loc
418
 
        if location is None:
419
 
            raise BzrCommandError("No pull location known or specified.")
420
 
        from branch import find_branch, DivergedBranches
 
563
            if stored_loc is None:
 
564
                raise BzrCommandError("No pull location known or specified.")
 
565
            else:
 
566
                print "Using last location: %s" % stored_loc
 
567
                location = stored_loc
 
568
        cache_root = tempfile.mkdtemp()
 
569
        from bzrlib.branch import DivergedBranches
421
570
        br_from = find_branch(location)
422
571
        location = pull_loc(br_from)
423
572
        old_revno = br_to.revno()
424
573
        try:
425
 
            br_to.update_revisions(br_from)
426
 
        except DivergedBranches:
427
 
            raise BzrCommandError("These branches have diverged.  Try merge.")
428
 
            
429
 
        merge(('.', -1), ('.', old_revno))
430
 
        if location != stored_loc:
431
 
            br_to.controlfile("x-pull", "wb").write(location + "\n")
 
574
            from branch import find_cached_branch, DivergedBranches
 
575
            br_from = find_cached_branch(location, cache_root)
 
576
            location = pull_loc(br_from)
 
577
            old_revno = br_to.revno()
 
578
            try:
 
579
                br_to.update_revisions(br_from)
 
580
            except DivergedBranches:
 
581
                raise BzrCommandError("These branches have diverged."
 
582
                    "  Try merge.")
 
583
                
 
584
            merge(('.', -1), ('.', old_revno), check_clean=False)
 
585
            if location != stored_loc:
 
586
                br_to.controlfile("x-pull", "wb").write(location + "\n")
 
587
        finally:
 
588
            rmtree(cache_root)
432
589
 
433
590
 
434
591
 
435
592
class cmd_branch(Command):
436
593
    """Create a new copy of a branch.
437
594
 
438
 
    If the TO_LOCATION is omitted, the last component of the
439
 
    FROM_LOCATION will be used.  In other words,
440
 
    "branch ../foo/bar" will attempt to create ./bar.
 
595
    If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
 
596
    be used.  In other words, "branch ../foo/bar" will attempt to create ./bar.
 
597
 
 
598
    To retrieve the branch as of a particular revision, supply the --revision
 
599
    parameter, as in "branch foo/bar -r 5".
441
600
    """
442
601
    takes_args = ['from_location', 'to_location?']
 
602
    takes_options = ['revision']
443
603
 
444
 
    def run(self, from_location, to_location=None):
 
604
    def run(self, from_location, to_location=None, revision=None):
445
605
        import errno
446
606
        from bzrlib.merge import merge
447
 
        
448
 
        if to_location is None:
449
 
            to_location = os.path.basename(from_location)
450
 
            # FIXME: If there's a trailing slash, keep removing them
451
 
            # until we find the right bit
452
 
 
453
 
        try:
454
 
            os.mkdir(to_location)
455
 
        except OSError, e:
456
 
            if e.errno == errno.EEXIST:
457
 
                raise BzrCommandError('Target directory "%s" already exists.' %
458
 
                                      to_location)
459
 
            if e.errno == errno.ENOENT:
460
 
                raise BzrCommandError('Parent of "%s" does not exist.' %
461
 
                                      to_location)
462
 
            else:
463
 
                raise
464
 
        br_to = Branch(to_location, init=True)
465
 
        from branch import find_branch, DivergedBranches
466
 
        try:
467
 
            br_from = find_branch(from_location)
468
 
        except OSError, e:
469
 
            if e.errno == errno.ENOENT:
470
 
                raise BzrCommandError('Source location "%s" does not exist.' %
471
 
                                      to_location)
472
 
            else:
473
 
                raise
474
 
 
475
 
        from_location = pull_loc(br_from)
476
 
        br_to.update_revisions(br_from)
477
 
        merge((to_location, -1), (to_location, 0), this_dir=to_location,
478
 
              check_clean=False)
479
 
        br_to.controlfile("x-pull", "wb").write(from_location + "\n")
 
607
        from bzrlib.branch import DivergedBranches, NoSuchRevision, \
 
608
             find_cached_branch, Branch
 
609
        from shutil import rmtree
 
610
        from meta_store import CachedStore
 
611
        import tempfile
 
612
        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
        try:
 
620
            try:
 
621
                br_from = find_cached_branch(from_location, cache_root)
 
622
            except OSError, e:
 
623
                if e.errno == errno.ENOENT:
 
624
                    raise BzrCommandError('Source location "%s" does not'
 
625
                                          ' exist.' % to_location)
 
626
                else:
 
627
                    raise
 
628
 
 
629
            if to_location is None:
 
630
                to_location = os.path.basename(from_location.rstrip("/\\"))
 
631
 
 
632
            try:
 
633
                os.mkdir(to_location)
 
634
            except OSError, e:
 
635
                if e.errno == errno.EEXIST:
 
636
                    raise BzrCommandError('Target directory "%s" already'
 
637
                                          ' exists.' % to_location)
 
638
                if e.errno == errno.ENOENT:
 
639
                    raise BzrCommandError('Parent of "%s" does not exist.' %
 
640
                                          to_location)
 
641
                else:
 
642
                    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")
 
664
        finally:
 
665
            rmtree(cache_root)
480
666
 
481
667
 
482
668
def pull_loc(branch):
499
685
    takes_args = ['dir?']
500
686
 
501
687
    def run(self, dir='.'):
502
 
        b = Branch(dir)
 
688
        b = find_branch(dir)
503
689
        old_inv = b.basis_tree().inventory
504
690
        new_inv = b.read_working_inventory()
505
691
 
516
702
    def run(self, branch=None):
517
703
        import info
518
704
 
519
 
        from branch import find_branch
520
705
        b = find_branch(branch)
521
706
        info.show_info(b)
522
707
 
531
716
    takes_options = ['verbose']
532
717
    
533
718
    def run(self, file_list, verbose=False):
534
 
        b = Branch(file_list[0])
 
719
        b = find_branch(file_list[0])
535
720
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
536
721
 
537
722
 
545
730
    hidden = True
546
731
    takes_args = ['filename']
547
732
    def run(self, filename):
548
 
        b = Branch(filename)
 
733
        b = find_branch(filename)
549
734
        i = b.inventory.path2id(b.relpath(filename))
550
735
        if i == None:
551
 
            bailout("%r is not a versioned file" % filename)
 
736
            raise BzrError("%r is not a versioned file" % filename)
552
737
        else:
553
738
            print i
554
739
 
561
746
    hidden = True
562
747
    takes_args = ['filename']
563
748
    def run(self, filename):
564
 
        b = Branch(filename)
 
749
        b = find_branch(filename)
565
750
        inv = b.inventory
566
751
        fid = inv.path2id(b.relpath(filename))
567
752
        if fid == None:
568
 
            bailout("%r is not a versioned file" % filename)
 
753
            raise BzrError("%r is not a versioned file" % filename)
569
754
        for fip in inv.get_idpath(fid):
570
755
            print fip
571
756
 
574
759
    """Display list of revision ids on this branch."""
575
760
    hidden = True
576
761
    def run(self):
577
 
        for patchid in Branch('.').revision_history():
 
762
        for patchid in find_branch('.').revision_history():
578
763
            print patchid
579
764
 
580
765
 
581
766
class cmd_directories(Command):
582
767
    """Display list of versioned directories in this branch."""
583
768
    def run(self):
584
 
        for name, ie in Branch('.').read_working_inventory().directories():
 
769
        for name, ie in find_branch('.').read_working_inventory().directories():
585
770
            if name == '':
586
771
                print '.'
587
772
            else:
602
787
        bzr commit -m 'imported project'
603
788
    """
604
789
    def run(self):
 
790
        from bzrlib.branch import Branch
605
791
        Branch('.', init=True)
606
792
 
607
793
 
631
817
    
632
818
    takes_args = ['file*']
633
819
    takes_options = ['revision', 'diff-options']
634
 
    aliases = ['di']
 
820
    aliases = ['di', 'dif']
635
821
 
636
822
    def run(self, revision=None, file_list=None, diff_options=None):
637
823
        from bzrlib.diff import show_diff
638
 
        from bzrlib import find_branch
639
824
 
640
825
        if file_list:
641
826
            b = find_branch(file_list[0])
644
829
                # just pointing to top-of-tree
645
830
                file_list = None
646
831
        else:
647
 
            b = Branch('.')
 
832
            b = find_branch('.')
 
833
 
 
834
        # TODO: Make show_diff support taking 2 arguments
 
835
        base_rev = None
 
836
        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]
648
840
    
649
 
        show_diff(b, revision, specific_files=file_list,
 
841
        show_diff(b, base_rev, specific_files=file_list,
650
842
                  external_diff_options=diff_options)
651
843
 
652
844
 
659
851
    TODO: Show files deleted since a previous revision, or between two revisions.
660
852
    """
661
853
    def run(self, show_ids=False):
662
 
        b = Branch('.')
 
854
        b = find_branch('.')
663
855
        old = b.basis_tree()
664
856
        new = b.working_tree()
665
857
 
680
872
    """List files modified in working tree."""
681
873
    hidden = True
682
874
    def run(self):
683
 
        import statcache
684
 
        b = Branch('.')
685
 
        inv = b.read_working_inventory()
686
 
        sc = statcache.update_cache(b, inv)
687
 
        basis = b.basis_tree()
688
 
        basis_inv = basis.inventory
689
 
        
690
 
        # We used to do this through iter_entries(), but that's slow
691
 
        # when most of the files are unmodified, as is usually the
692
 
        # case.  So instead we iterate by inventory entry, and only
693
 
        # calculate paths as necessary.
694
 
 
695
 
        for file_id in basis_inv:
696
 
            cacheentry = sc.get(file_id)
697
 
            if not cacheentry:                 # deleted
698
 
                continue
699
 
            ie = basis_inv[file_id]
700
 
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
701
 
                path = inv.id2path(file_id)
702
 
                print path
 
875
        from bzrlib.diff import compare_trees
 
876
 
 
877
        b = find_branch('.')
 
878
        td = compare_trees(b.basis_tree(), b.working_tree())
 
879
 
 
880
        for path, id, kind in td.modified:
 
881
            print path
703
882
 
704
883
 
705
884
 
707
886
    """List files added in working tree."""
708
887
    hidden = True
709
888
    def run(self):
710
 
        b = Branch('.')
 
889
        b = find_branch('.')
711
890
        wt = b.working_tree()
712
891
        basis_inv = b.basis_tree().inventory
713
892
        inv = wt.inventory
729
908
    takes_args = ['filename?']
730
909
    def run(self, filename=None):
731
910
        """Print the branch root."""
732
 
        from branch import find_branch
733
911
        b = find_branch(filename)
734
912
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
735
913
 
741
919
    -r revision requests a specific revision, -r :end or -r begin: are
742
920
    also valid.
743
921
 
 
922
    --message allows you to give a regular expression, which will be evaluated
 
923
    so that only matching entries will be displayed.
 
924
 
744
925
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
745
926
  
746
927
    """
747
928
 
748
929
    takes_args = ['filename?']
749
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
 
930
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision','long', 'message']
750
931
    
751
932
    def run(self, filename=None, timezone='original',
752
933
            verbose=False,
753
934
            show_ids=False,
754
935
            forward=False,
755
 
            revision=None):
756
 
        from bzrlib import show_log, find_branch
 
936
            revision=None,
 
937
            message=None,
 
938
            long=False):
 
939
        from bzrlib.branch import find_branch
 
940
        from bzrlib.log import log_formatter, show_log
757
941
        import codecs
758
942
 
759
943
        direction = (forward and 'forward') or 'reverse'
769
953
            b = find_branch('.')
770
954
            file_id = None
771
955
 
772
 
        if revision == None:
773
 
            revision = [None, None]
774
 
        elif isinstance(revision, int):
775
 
            revision = [revision, revision]
 
956
        if revision is None:
 
957
            rev1 = None
 
958
            rev2 = None
 
959
        elif len(revision) == 1:
 
960
            rev1 = rev2 = b.get_revision_info(revision[0])[0]
 
961
        elif len(revision) == 2:
 
962
            rev1 = b.get_revision_info(revision[0])[0]
 
963
            rev2 = b.get_revision_info(revision[1])[0]
776
964
        else:
777
 
            # pair of revisions?
778
 
            pass
779
 
            
780
 
        assert len(revision) == 2
 
965
            raise BzrCommandError('bzr log --revision takes one or two values.')
 
966
 
 
967
        if rev1 == 0:
 
968
            rev1 = None
 
969
        if rev2 == 0:
 
970
            rev2 = None
781
971
 
782
972
        mutter('encoding log as %r' % bzrlib.user_encoding)
783
973
 
785
975
        # in e.g. the default C locale.
786
976
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
787
977
 
788
 
        show_log(b, file_id,
789
 
                 show_timezone=timezone,
 
978
        if long:
 
979
            log_format = 'long'
 
980
        else:
 
981
            log_format = 'short'
 
982
        lf = log_formatter(log_format,
 
983
                           show_ids=show_ids,
 
984
                           to_file=outf,
 
985
                           show_timezone=timezone)
 
986
 
 
987
        show_log(b,
 
988
                 lf,
 
989
                 file_id,
790
990
                 verbose=verbose,
791
 
                 show_ids=show_ids,
792
 
                 to_file=outf,
793
991
                 direction=direction,
794
 
                 start_revision=revision[0],
795
 
                 end_revision=revision[1])
 
992
                 start_revision=rev1,
 
993
                 end_revision=rev2,
 
994
                 search=message)
796
995
 
797
996
 
798
997
 
803
1002
    hidden = True
804
1003
    takes_args = ["filename"]
805
1004
    def run(self, filename):
806
 
        b = Branch(filename)
 
1005
        b = find_branch(filename)
807
1006
        inv = b.read_working_inventory()
808
1007
        file_id = inv.path2id(b.relpath(filename))
809
1008
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
817
1016
    """
818
1017
    hidden = True
819
1018
    def run(self, revision=None, verbose=False):
820
 
        b = Branch('.')
 
1019
        b = find_branch('.')
821
1020
        if revision == None:
822
1021
            tree = b.working_tree()
823
1022
        else:
841
1040
class cmd_unknowns(Command):
842
1041
    """List unknown files."""
843
1042
    def run(self):
844
 
        for f in Branch('.').unknowns():
 
1043
        from bzrlib.osutils import quotefn
 
1044
        for f in find_branch('.').unknowns():
845
1045
            print quotefn(f)
846
1046
 
847
1047
 
869
1069
        from bzrlib.atomicfile import AtomicFile
870
1070
        import os.path
871
1071
 
872
 
        b = Branch('.')
 
1072
        b = find_branch('.')
873
1073
        ifn = b.abspath('.bzrignore')
874
1074
 
875
1075
        if os.path.exists(ifn):
909
1109
 
910
1110
    See also: bzr ignore"""
911
1111
    def run(self):
912
 
        tree = Branch('.').working_tree()
 
1112
        tree = find_branch('.').working_tree()
913
1113
        for path, file_class, kind, file_id in tree.list_files():
914
1114
            if file_class != 'I':
915
1115
                continue
933
1133
        except ValueError:
934
1134
            raise BzrCommandError("not a valid revision-number: %r" % revno)
935
1135
 
936
 
        print Branch('.').lookup_revision(revno)
 
1136
        print find_branch('.').lookup_revision(revno)
937
1137
 
938
1138
 
939
1139
class cmd_export(Command):
940
1140
    """Export past revision to destination directory.
941
1141
 
942
 
    If no revision is specified this exports the last committed revision."""
 
1142
    If no revision is specified this exports the last committed revision.
 
1143
 
 
1144
    Format may be an "exporter" name, such as tar, tgz, tbz2.  If none is
 
1145
    given, try to find the format with the extension. If no extension
 
1146
    is found exports to a directory (equivalent to --format=dir).
 
1147
 
 
1148
    Root may be the top directory for tar, tgz and tbz2 formats. If none
 
1149
    is given, the top directory will be the root name of the file."""
 
1150
    # TODO: list known exporters
943
1151
    takes_args = ['dest']
944
 
    takes_options = ['revision']
945
 
    def run(self, dest, revision=None):
946
 
        b = Branch('.')
947
 
        if revision == None:
948
 
            rh = b.revision_history()[-1]
 
1152
    takes_options = ['revision', 'format', 'root']
 
1153
    def run(self, dest, revision=None, format=None, root=None):
 
1154
        import os.path
 
1155
        b = find_branch('.')
 
1156
        if revision is None:
 
1157
            rev_id = b.last_patch()
949
1158
        else:
950
 
            rh = b.lookup_revision(int(revision))
951
 
        t = b.revision_tree(rh)
952
 
        t.export(dest)
 
1159
            if len(revision) != 1:
 
1160
                raise BzrError('bzr export --revision takes exactly 1 argument')
 
1161
            revno, rev_id = b.get_revision_info(revision[0])
 
1162
        t = b.revision_tree(rev_id)
 
1163
        root, ext = os.path.splitext(dest)
 
1164
        if not format:
 
1165
            if ext in (".tar",):
 
1166
                format = "tar"
 
1167
            elif ext in (".gz", ".tgz"):
 
1168
                format = "tgz"
 
1169
            elif ext in (".bz2", ".tbz2"):
 
1170
                format = "tbz2"
 
1171
            else:
 
1172
                format = "dir"
 
1173
        t.export(dest, format, root)
953
1174
 
954
1175
 
955
1176
class cmd_cat(Command):
961
1182
    def run(self, filename, revision=None):
962
1183
        if revision == None:
963
1184
            raise BzrCommandError("bzr cat requires a revision number")
964
 
        b = Branch('.')
965
 
        b.print_file(b.relpath(filename), int(revision))
 
1185
        elif len(revision) != 1:
 
1186
            raise BzrCommandError("bzr cat --revision takes exactly one number")
 
1187
        b = find_branch('.')
 
1188
        b.print_file(b.relpath(filename), revision[0])
966
1189
 
967
1190
 
968
1191
class cmd_local_time_offset(Command):
989
1212
    TODO: Strict commit that fails if there are unknown or deleted files.
990
1213
    """
991
1214
    takes_args = ['selected*']
992
 
    takes_options = ['message', 'file', 'verbose']
 
1215
    takes_options = ['message', 'file', 'verbose', 'unchanged']
993
1216
    aliases = ['ci', 'checkin']
994
1217
 
995
 
    def run(self, message=None, file=None, verbose=True, selected_list=None):
996
 
        from bzrlib.commit import commit
 
1218
    def run(self, message=None, file=None, verbose=True, selected_list=None,
 
1219
            unchanged=False):
 
1220
        from bzrlib.errors import PointlessCommit
 
1221
        from bzrlib.osutils import get_text_message
997
1222
 
998
1223
        ## Warning: shadows builtin file()
999
1224
        if not message and not file:
1000
 
            raise BzrCommandError("please specify a commit message",
1001
 
                                  ["use either --message or --file"])
 
1225
            import cStringIO
 
1226
            stdout = sys.stdout
 
1227
            catcher = cStringIO.StringIO()
 
1228
            sys.stdout = catcher
 
1229
            cmd_status({"file_list":selected_list}, {})
 
1230
            info = catcher.getvalue()
 
1231
            sys.stdout = stdout
 
1232
            message = get_text_message(info)
 
1233
            
 
1234
            if message is None:
 
1235
                raise BzrCommandError("please specify a commit message",
 
1236
                                      ["use either --message or --file"])
1002
1237
        elif message and file:
1003
1238
            raise BzrCommandError("please specify either --message or --file")
1004
1239
        
1006
1241
            import codecs
1007
1242
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1008
1243
 
1009
 
        b = Branch('.')
1010
 
        commit(b, message, verbose=verbose, specific_files=selected_list)
 
1244
        b = find_branch('.')
 
1245
 
 
1246
        try:
 
1247
            b.commit(message, verbose=verbose,
 
1248
                     specific_files=selected_list,
 
1249
                     allow_pointless=unchanged)
 
1250
        except PointlessCommit:
 
1251
            # FIXME: This should really happen before the file is read in;
 
1252
            # perhaps prepare the commit; get the message; then actually commit
 
1253
            raise BzrCommandError("no changes to commit",
 
1254
                                  ["use --unchanged to commit anyhow"])
1011
1255
 
1012
1256
 
1013
1257
class cmd_check(Command):
1015
1259
 
1016
1260
    This command checks various invariants about the branch storage to
1017
1261
    detect data corruption or bzr bugs.
1018
 
    """
1019
 
    takes_args = ['dir?']
1020
 
    def run(self, dir='.'):
1021
 
        import bzrlib.check
1022
 
        bzrlib.check.check(Branch(dir))
 
1262
 
 
1263
    If given the --update flag, it will update some optional fields
 
1264
    to help ensure data consistency.
 
1265
    """
 
1266
    takes_args = ['dir?']
 
1267
 
 
1268
    def run(self, dir='.'):
 
1269
        from bzrlib.check import check
 
1270
        check(find_branch(dir))
 
1271
 
 
1272
 
 
1273
 
 
1274
class cmd_scan_cache(Command):
 
1275
    hidden = True
 
1276
    def run(self):
 
1277
        from bzrlib.hashcache import HashCache
 
1278
        import os
 
1279
 
 
1280
        c = HashCache('.')
 
1281
        c.read()
 
1282
        c.scan()
 
1283
            
 
1284
        print '%6d stats' % c.stat_count
 
1285
        print '%6d in hashcache' % len(c._cache)
 
1286
        print '%6d files removed from cache' % c.removed_count
 
1287
        print '%6d hashes updated' % c.update_count
 
1288
        print '%6d files changed too recently to cache' % c.danger_count
 
1289
 
 
1290
        if c.needs_write:
 
1291
            c.write()
 
1292
            
 
1293
 
 
1294
 
 
1295
class cmd_upgrade(Command):
 
1296
    """Upgrade branch storage to current format.
 
1297
 
 
1298
    This should normally be used only after the check command tells
 
1299
    you to run it.
 
1300
    """
 
1301
    takes_args = ['dir?']
 
1302
 
 
1303
    def run(self, dir='.'):
 
1304
        from bzrlib.upgrade import upgrade
 
1305
        upgrade(find_branch(dir))
1023
1306
 
1024
1307
 
1025
1308
 
1037
1320
class cmd_selftest(Command):
1038
1321
    """Run internal test suite"""
1039
1322
    hidden = True
1040
 
    def run(self):
 
1323
    takes_options = ['verbose']
 
1324
    def run(self, verbose=False):
1041
1325
        from bzrlib.selftest import selftest
1042
 
        if selftest():
1043
 
            return 0
1044
 
        else:
1045
 
            return 1
1046
 
 
 
1326
        return int(not selftest(verbose=verbose))
1047
1327
 
1048
1328
 
1049
1329
class cmd_version(Command):
1081
1361
    ['..', -1]
1082
1362
    >>> parse_spec("../f/@35")
1083
1363
    ['../f', 35]
 
1364
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
1365
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
1084
1366
    """
1085
1367
    if spec is None:
1086
1368
        return [None, None]
1090
1372
        if parsed[1] == "":
1091
1373
            parsed[1] = -1
1092
1374
        else:
1093
 
            parsed[1] = int(parsed[1])
1094
 
            assert parsed[1] >=0
 
1375
            try:
 
1376
                parsed[1] = int(parsed[1])
 
1377
            except ValueError:
 
1378
                pass # We can allow stuff like ./@revid:blahblahblah
 
1379
            else:
 
1380
                assert parsed[1] >=0
1095
1381
    else:
1096
1382
        parsed = [spec, None]
1097
1383
    return parsed
1121
1407
    --force is given.
1122
1408
    """
1123
1409
    takes_args = ['other_spec', 'base_spec?']
1124
 
    takes_options = ['force']
 
1410
    takes_options = ['force', 'merge-type']
1125
1411
 
1126
 
    def run(self, other_spec, base_spec=None, force=False):
 
1412
    def run(self, other_spec, base_spec=None, force=False, merge_type=None):
1127
1413
        from bzrlib.merge import merge
 
1414
        from bzrlib.merge_core import ApplyMerge3
 
1415
        if merge_type is None:
 
1416
            merge_type = ApplyMerge3
1128
1417
        merge(parse_spec(other_spec), parse_spec(base_spec),
1129
 
              check_clean=(not force))
 
1418
              check_clean=(not force), merge_type=merge_type)
 
1419
 
1130
1420
 
1131
1421
 
1132
1422
class cmd_revert(Command):
 
1423
    """Restore selected files from a previous revision.
 
1424
    """
 
1425
    takes_args = ['file+']
 
1426
    def run(self, file_list):
 
1427
        from bzrlib.branch import find_branch
 
1428
        
 
1429
        if not file_list:
 
1430
            file_list = ['.']
 
1431
            
 
1432
        b = find_branch(file_list[0])
 
1433
 
 
1434
        b.revert([b.relpath(f) for f in file_list])
 
1435
 
 
1436
 
 
1437
class cmd_merge_revert(Command):
1133
1438
    """Reverse all changes since the last commit.
1134
1439
 
1135
 
    Only versioned files are affected.
1136
 
 
1137
 
    TODO: Store backups of any files that will be reverted, so
1138
 
          that the revert can be undone.          
 
1440
    Only versioned files are affected.  By default, any files that are changed
 
1441
    will be backed up first.  Backup files have a '~' appended to their name.
1139
1442
    """
1140
 
    takes_options = ['revision']
 
1443
    takes_options = ['revision', 'no-backup']
1141
1444
 
1142
 
    def run(self, revision=-1):
1143
 
        merge(('.', revision), parse_spec('.'),
 
1445
    def run(self, revision=None, no_backup=False):
 
1446
        from bzrlib.merge import merge
 
1447
        if revision is None:
 
1448
            revision = [-1]
 
1449
        elif len(revision) != 1:
 
1450
            raise BzrCommandError('bzr merge-revert --revision takes exactly 1 argument')
 
1451
        merge(('.', revision[0]), parse_spec('.'),
1144
1452
              check_clean=False,
1145
 
              ignore_zero=True)
 
1453
              ignore_zero=True,
 
1454
              backup_files=not no_backup)
1146
1455
 
1147
1456
 
1148
1457
class cmd_assert_fail(Command):
1164
1473
        help.help(topic)
1165
1474
 
1166
1475
 
1167
 
class cmd_update_stat_cache(Command):
1168
 
    """Update stat-cache mapping inodes to SHA-1 hashes.
1169
 
 
1170
 
    For testing only."""
 
1476
 
 
1477
 
 
1478
class cmd_plugins(Command):
 
1479
    """List plugins"""
1171
1480
    hidden = True
1172
1481
    def run(self):
1173
 
        import statcache
1174
 
        b = Branch('.')
1175
 
        statcache.update_cache(b.base, b.read_working_inventory())
 
1482
        import bzrlib.plugin
 
1483
        from inspect import getdoc
 
1484
        from pprint import pprint
 
1485
        for plugin in bzrlib.plugin.all_plugins:
 
1486
            print plugin.__path__[0]
 
1487
            d = getdoc(plugin)
 
1488
            if d:
 
1489
                print '\t', d.split('\n')[0]
 
1490
 
 
1491
        #pprint(bzrlib.plugin.all_plugins)
1176
1492
 
1177
1493
 
1178
1494
 
1185
1501
    'help':                   None,
1186
1502
    'file':                   unicode,
1187
1503
    'force':                  None,
 
1504
    'format':                 unicode,
1188
1505
    'forward':                None,
1189
1506
    'message':                unicode,
1190
1507
    'no-recurse':             None,
1195
1512
    'verbose':                None,
1196
1513
    'version':                None,
1197
1514
    'email':                  None,
 
1515
    'unchanged':              None,
 
1516
    'update':                 None,
 
1517
    'long':                   None,
 
1518
    'root':                   str,
 
1519
    'no-backup':              None,
 
1520
    'merge-type':             get_merge_type,
1198
1521
    }
1199
1522
 
1200
1523
SHORT_OPTIONS = {
1203
1526
    'm':                      'message',
1204
1527
    'r':                      'revision',
1205
1528
    'v':                      'verbose',
 
1529
    'l':                      'long',
1206
1530
}
1207
1531
 
1208
1532
 
1222
1546
    (['status'], {'all': True})
1223
1547
    >>> parse_args('commit --message=biter'.split())
1224
1548
    (['commit'], {'message': u'biter'})
 
1549
    >>> parse_args('log -r 500'.split())
 
1550
    (['log'], {'revision': [500]})
 
1551
    >>> parse_args('log -r500..600'.split())
 
1552
    (['log'], {'revision': [500, 600]})
 
1553
    >>> parse_args('log -vr500..600'.split())
 
1554
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
1555
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
1556
    (['log'], {'revision': ['v500', 600]})
1225
1557
    """
1226
1558
    args = []
1227
1559
    opts = {}
1241
1573
                else:
1242
1574
                    optname = a[2:]
1243
1575
                if optname not in OPTIONS:
1244
 
                    bailout('unknown long option %r' % a)
 
1576
                    raise BzrError('unknown long option %r' % a)
1245
1577
            else:
1246
1578
                shortopt = a[1:]
1247
 
                if shortopt not in SHORT_OPTIONS:
1248
 
                    bailout('unknown short option %r' % a)
1249
 
                optname = SHORT_OPTIONS[shortopt]
 
1579
                if shortopt in SHORT_OPTIONS:
 
1580
                    # Multi-character options must have a space to delimit
 
1581
                    # their value
 
1582
                    optname = SHORT_OPTIONS[shortopt]
 
1583
                else:
 
1584
                    # Single character short options, can be chained,
 
1585
                    # and have their value appended to their name
 
1586
                    shortopt = a[1:2]
 
1587
                    if shortopt not in SHORT_OPTIONS:
 
1588
                        # We didn't find the multi-character name, and we
 
1589
                        # didn't find the single char name
 
1590
                        raise BzrError('unknown short option %r' % a)
 
1591
                    optname = SHORT_OPTIONS[shortopt]
 
1592
 
 
1593
                    if a[2:]:
 
1594
                        # There are extra things on this option
 
1595
                        # see if it is the value, or if it is another
 
1596
                        # short option
 
1597
                        optargfn = OPTIONS[optname]
 
1598
                        if optargfn is None:
 
1599
                            # This option does not take an argument, so the
 
1600
                            # next entry is another short option, pack it back
 
1601
                            # into the list
 
1602
                            argv.insert(0, '-' + a[2:])
 
1603
                        else:
 
1604
                            # This option takes an argument, so pack it
 
1605
                            # into the array
 
1606
                            optarg = a[2:]
1250
1607
            
1251
1608
            if optname in opts:
1252
1609
                # XXX: Do we ever want to support this, e.g. for -r?
1253
 
                bailout('repeated option %r' % a)
 
1610
                raise BzrError('repeated option %r' % a)
1254
1611
                
1255
1612
            optargfn = OPTIONS[optname]
1256
1613
            if optargfn:
1257
1614
                if optarg == None:
1258
1615
                    if not argv:
1259
 
                        bailout('option %r needs an argument' % a)
 
1616
                        raise BzrError('option %r needs an argument' % a)
1260
1617
                    else:
1261
1618
                        optarg = argv.pop(0)
1262
1619
                opts[optname] = optargfn(optarg)
1263
1620
            else:
1264
1621
                if optarg != None:
1265
 
                    bailout('option %r takes no argument' % optname)
 
1622
                    raise BzrError('option %r takes no argument' % optname)
1266
1623
                opts[optname] = True
1267
1624
        else:
1268
1625
            args.append(a)
1316
1673
    return argdict
1317
1674
 
1318
1675
 
 
1676
def _parse_master_args(argv):
 
1677
    """Parse the arguments that always go with the original command.
 
1678
    These are things like bzr --no-plugins, etc.
 
1679
 
 
1680
    There are now 2 types of option flags. Ones that come *before* the command,
 
1681
    and ones that come *after* the command.
 
1682
    Ones coming *before* the command are applied against all possible commands.
 
1683
    And are generally applied before plugins are loaded.
 
1684
 
 
1685
    The current list are:
 
1686
        --builtin   Allow plugins to load, but don't let them override builtin commands,
 
1687
                    they will still be allowed if they do not override a builtin.
 
1688
        --no-plugins    Don't load any plugins. This lets you get back to official source
 
1689
                        behavior.
 
1690
        --profile   Enable the hotspot profile before running the command.
 
1691
                    For backwards compatibility, this is also a non-master option.
 
1692
        --version   Spit out the version of bzr that is running and exit.
 
1693
                    This is also a non-master option.
 
1694
        --help      Run help and exit, also a non-master option (I think that should stay, though)
 
1695
 
 
1696
    >>> argv, opts = _parse_master_args(['--test'])
 
1697
    Traceback (most recent call last):
 
1698
    ...
 
1699
    BzrCommandError: Invalid master option: 'test'
 
1700
    >>> argv, opts = _parse_master_args(['--version', 'command'])
 
1701
    >>> print argv
 
1702
    ['command']
 
1703
    >>> print opts['version']
 
1704
    True
 
1705
    >>> argv, opts = _parse_master_args(['--profile', 'command', '--more-options'])
 
1706
    >>> print argv
 
1707
    ['command', '--more-options']
 
1708
    >>> print opts['profile']
 
1709
    True
 
1710
    >>> argv, opts = _parse_master_args(['--no-plugins', 'command'])
 
1711
    >>> print argv
 
1712
    ['command']
 
1713
    >>> print opts['no-plugins']
 
1714
    True
 
1715
    >>> print opts['profile']
 
1716
    False
 
1717
    >>> argv, opts = _parse_master_args(['command', '--profile'])
 
1718
    >>> print argv
 
1719
    ['command', '--profile']
 
1720
    >>> print opts['profile']
 
1721
    False
 
1722
    """
 
1723
    master_opts = {'builtin':False,
 
1724
        'no-plugins':False,
 
1725
        'version':False,
 
1726
        'profile':False,
 
1727
        'help':False
 
1728
    }
 
1729
 
 
1730
    for arg in argv[:]:
 
1731
        if arg[:2] != '--': # at the first non-option, we return the rest
 
1732
            break
 
1733
        arg = arg[2:] # Remove '--'
 
1734
        if arg not in master_opts:
 
1735
            # We could say that this is not an error, that we should
 
1736
            # just let it be handled by the main section instead
 
1737
            raise BzrCommandError('Invalid master option: %r' % arg)
 
1738
        argv.pop(0) # We are consuming this entry
 
1739
        master_opts[arg] = True
 
1740
    return argv, master_opts
 
1741
 
 
1742
 
1319
1743
 
1320
1744
def run_bzr(argv):
1321
1745
    """Execute a command.
1322
1746
 
1323
1747
    This is similar to main(), but without all the trappings for
1324
1748
    logging and error handling.  
 
1749
    
 
1750
    argv
 
1751
       The command-line arguments, without the program name.
 
1752
    
 
1753
    Returns a command status or raises an exception.
1325
1754
    """
1326
1755
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
1756
 
 
1757
    # some options like --builtin and --no-plugins have special effects
 
1758
    argv, master_opts = _parse_master_args(argv)
 
1759
    if not master_opts['no-plugins']:
 
1760
        from bzrlib.plugin import load_plugins
 
1761
        load_plugins()
 
1762
 
 
1763
    args, opts = parse_args(argv)
 
1764
 
 
1765
    if master_opts.get('help') or 'help' in opts:
 
1766
        from bzrlib.help import help
 
1767
        if argv:
 
1768
            help(argv[0])
 
1769
        else:
 
1770
            help()
 
1771
        return 0            
 
1772
        
 
1773
    if 'version' in opts:
 
1774
        show_version()
 
1775
        return 0
 
1776
    
 
1777
    if args and args[0] == 'builtin':
 
1778
        include_plugins=False
 
1779
        args = args[1:]
1327
1780
    
1328
1781
    try:
1329
 
        args, opts = parse_args(argv[1:])
1330
 
        if 'help' in opts:
1331
 
            import help
1332
 
            if args:
1333
 
                help.help(args[0])
1334
 
            else:
1335
 
                help.help()
1336
 
            return 0
1337
 
        elif 'version' in opts:
1338
 
            show_version()
1339
 
            return 0
1340
1782
        cmd = str(args.pop(0))
1341
1783
    except IndexError:
1342
 
        import help
1343
 
        help.help()
 
1784
        print >>sys.stderr, "please try 'bzr help' for help"
1344
1785
        return 1
1345
 
          
1346
 
 
1347
 
    canonical_cmd, cmd_class = get_cmd_class(cmd)
1348
 
 
1349
 
    # global option
 
1786
 
 
1787
    plugins_override = not (master_opts['builtin'])
 
1788
    canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
 
1789
 
 
1790
    profile = master_opts['profile']
 
1791
    # For backwards compatibility, I would rather stick with --profile being a
 
1792
    # master/global option
1350
1793
    if 'profile' in opts:
1351
1794
        profile = True
1352
1795
        del opts['profile']
1353
 
    else:
1354
 
        profile = False
1355
1796
 
1356
1797
    # check options are reasonable
1357
1798
    allowed = cmd_class.takes_options
1406
1847
 
1407
1848
 
1408
1849
def main(argv):
1409
 
    import errno
1410
1850
    
1411
 
    bzrlib.open_tracefile(argv)
 
1851
    bzrlib.trace.open_tracefile(argv)
1412
1852
 
1413
1853
    try:
1414
1854
        try:
1415
1855
            try:
1416
 
                return run_bzr(argv)
 
1856
                return run_bzr(argv[1:])
1417
1857
            finally:
1418
1858
                # do this here inside the exception wrappers to catch EPIPE
1419
1859
                sys.stdout.flush()
1435
1875
            _report_exception('interrupted', quiet=True)
1436
1876
            return 2
1437
1877
        except Exception, e:
 
1878
            import errno
1438
1879
            quiet = False
1439
1880
            if (isinstance(e, IOError) 
1440
1881
                and hasattr(e, 'errno')