~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, pumpfile, isdir, isfile
25
 
from bzrlib.tree import RevisionTree, EmptyTree, Tree
26
 
from bzrlib.revision import Revision
27
 
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
28
 
     format_date
29
 
from bzrlib import merge
 
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__])
30
44
 
31
45
 
32
46
def _squish_command_name(cmd):
37
51
    assert cmd.startswith("cmd_")
38
52
    return cmd[4:].replace('_','-')
39
53
 
 
54
 
40
55
def _parse_revision_str(revstr):
41
 
    """This handles a revision string -> revno. 
42
 
 
43
 
    There are several possibilities:
44
 
 
45
 
        '234'       -> 234
46
 
        '234:345'   -> [234, 345]
47
 
        ':234'      -> [None, 234]
48
 
        '234:'      -> [234, None]
49
 
 
50
 
    In the future we will also support:
51
 
        'uuid:blah-blah-blah'   -> ?
52
 
        'hash:blahblahblah'     -> ?
53
 
        potentially:
54
 
        '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']
55
99
    """
56
 
    if revstr.find(':') != -1:
57
 
        revs = revstr.split(':')
58
 
        if len(revs) > 2:
59
 
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
60
 
 
61
 
        if not revs[0]:
62
 
            revs[0] = None
63
 
        else:
64
 
            revs[0] = int(revs[0])
65
 
 
66
 
        if not revs[1]:
67
 
            revs[1] = None
68
 
        else:
69
 
            revs[1] = int(revs[1])
70
 
    else:
71
 
        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)
72
122
    return revs
73
123
 
74
 
def get_all_cmds():
75
 
    """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 = {}
76
142
    for k, v in globals().iteritems():
77
143
        if k.startswith("cmd_"):
78
 
            yield _unsquish_command_name(k), v
79
 
 
80
 
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):
81
162
    """Return the canonical name and command class for a command.
82
163
    """
83
164
    cmd = str(cmd)                      # not unicode
84
165
 
85
166
    # first look up this command under the specified name
 
167
    cmds = _get_cmd_dict(plugins_override=plugins_override)
86
168
    try:
87
 
        return cmd, globals()[_squish_command_name(cmd)]
 
169
        return cmd, cmds[cmd]
88
170
    except KeyError:
89
171
        pass
90
172
 
91
173
    # look for any command which claims this as an alias
92
 
    for cmdname, cmdclass in get_all_cmds():
 
174
    for cmdname, cmdclass in cmds.iteritems():
93
175
        if cmd in cmdclass.aliases:
94
176
            return cmdname, cmdclass
95
177
 
132
214
        assert isinstance(arguments, dict)
133
215
        cmdargs = options.copy()
134
216
        cmdargs.update(arguments)
135
 
        assert self.__doc__ != Command.__doc__, \
136
 
               ("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)
137
220
        self.status = self.run(**cmdargs)
 
221
        if self.status is None:
 
222
            self.status = 0
138
223
 
139
224
    
140
225
    def run(self):
168
253
        import os.path
169
254
        bzrpath = os.environ.get('BZRPATH', '')
170
255
 
171
 
        for dir in bzrpath.split(':'):
 
256
        for dir in bzrpath.split(os.pathsep):
172
257
            path = os.path.join(dir, cmd)
173
258
            if os.path.isfile(path):
174
259
                return ExternalCommand(path)
180
265
    def __init__(self, path):
181
266
        self.path = path
182
267
 
183
 
        # TODO: If either of these fail, we should detect that and
184
 
        # assume that path is not really a bzr plugin after all.
185
 
 
186
268
        pipe = os.popen('%s --bzr-usage' % path, 'r')
187
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?
188
277
        self.takes_args = pipe.readline().split()
189
 
        pipe.close()
 
278
 
 
279
        if pipe.close() is not None:
 
280
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
190
281
 
191
282
        pipe = os.popen('%s --bzr-help' % path, 'r')
192
283
        self.__doc__ = pipe.read()
193
 
        pipe.close()
 
284
        if pipe.close() is not None:
 
285
            raise BzrError("Failed funning '%s --bzr-help'" % path)
194
286
 
195
287
    def __call__(self, options, arguments):
196
288
        Command.__init__(self, options, arguments)
203
295
        keys = kargs.keys()
204
296
        keys.sort()
205
297
        for name in keys:
 
298
            optname = name.replace('_','-')
206
299
            value = kargs[name]
207
 
            if OPTIONS.has_key(name):
 
300
            if OPTIONS.has_key(optname):
208
301
                # it's an option
209
 
                opts.append('--%s' % name)
 
302
                opts.append('--%s' % optname)
210
303
                if value is not None and value is not True:
211
304
                    opts.append(str(value))
212
305
            else:
256
349
    directory is shown.  Otherwise, only the status of the specified
257
350
    files or directories is reported.  If a directory is given, status
258
351
    is reported for everything inside that directory.
 
352
 
 
353
    If a revision is specified, the changes since that revision are shown.
259
354
    """
260
355
    takes_args = ['file*']
261
 
    takes_options = ['all', 'show-ids']
 
356
    takes_options = ['all', 'show-ids', 'revision']
262
357
    aliases = ['st', 'stat']
263
358
    
264
359
    def run(self, all=False, show_ids=False, file_list=None):
265
360
        if file_list:
266
 
            b = Branch(file_list[0], lock_mode='r')
 
361
            b = find_branch(file_list[0])
267
362
            file_list = [b.relpath(x) for x in file_list]
268
363
            # special case: only one path was given and it's the root
269
364
            # of the branch
270
365
            if file_list == ['']:
271
366
                file_list = None
272
367
        else:
273
 
            b = Branch('.', lock_mode='r')
274
 
        import status
275
 
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
276
 
                           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)
277
373
 
278
374
 
279
375
class cmd_cat_revision(Command):
283
379
    takes_args = ['revision_id']
284
380
    
285
381
    def run(self, revision_id):
286
 
        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)
287
384
 
288
385
 
289
386
class cmd_revno(Command):
291
388
 
292
389
    This is equal to the number of revisions on this branch."""
293
390
    def run(self):
294
 
        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)
295
414
 
296
415
    
297
416
class cmd_add(Command):
307
426
    whether already versioned or not, are searched for files or
308
427
    subdirectories that are neither versioned or ignored, and these
309
428
    are added.  This search proceeds recursively into versioned
310
 
    directories.
 
429
    directories.  If no names are given '.' is assumed.
311
430
 
312
 
    Therefore simply saying 'bzr add .' will version all files that
 
431
    Therefore simply saying 'bzr add' will version all files that
313
432
    are currently unknown.
314
433
 
315
434
    TODO: Perhaps adding a file whose directly is not versioned should
316
435
    recursively add that parent, rather than giving an error?
317
436
    """
318
 
    takes_args = ['file+']
319
 
    takes_options = ['verbose']
 
437
    takes_args = ['file*']
 
438
    takes_options = ['verbose', 'no-recurse']
320
439
    
321
 
    def run(self, file_list, verbose=False):
322
 
        bzrlib.add.smart_add(file_list, verbose)
 
440
    def run(self, file_list, verbose=False, no_recurse=False):
 
441
        from bzrlib.add import smart_add
 
442
        smart_add(file_list, verbose, not no_recurse)
 
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)
323
461
 
324
462
 
325
463
class cmd_relpath(Command):
326
464
    """Show path of a file relative to root"""
327
465
    takes_args = ['filename']
 
466
    hidden = True
328
467
    
329
468
    def run(self, filename):
330
 
        print Branch(filename).relpath(filename)
 
469
        print find_branch(filename).relpath(filename)
331
470
 
332
471
 
333
472
 
334
473
class cmd_inventory(Command):
335
474
    """Show inventory of the current working copy or a revision."""
336
 
    takes_options = ['revision']
 
475
    takes_options = ['revision', 'show-ids']
337
476
    
338
 
    def run(self, revision=None):
339
 
        b = Branch('.')
 
477
    def run(self, revision=None, show_ids=False):
 
478
        b = find_branch('.')
340
479
        if revision == None:
341
480
            inv = b.read_working_inventory()
342
481
        else:
343
 
            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]))
344
486
 
345
487
        for path, entry in inv.entries():
346
 
            print '%-50s %s' % (entry.file_id, path)
 
488
            if show_ids:
 
489
                print '%-50s %s' % (path, entry.file_id)
 
490
            else:
 
491
                print path
347
492
 
348
493
 
349
494
class cmd_move(Command):
356
501
    """
357
502
    takes_args = ['source$', 'dest']
358
503
    def run(self, source_list, dest):
359
 
        b = Branch('.')
 
504
        b = find_branch('.')
360
505
 
361
506
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
362
507
 
378
523
    takes_args = ['from_name', 'to_name']
379
524
    
380
525
    def run(self, from_name, to_name):
381
 
        b = Branch('.')
 
526
        b = find_branch('.')
382
527
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
383
528
 
384
529
 
385
530
 
 
531
 
 
532
 
 
533
class cmd_pull(Command):
 
534
    """Pull any changes from another branch into the current one.
 
535
 
 
536
    If the location is omitted, the last-used location will be used.
 
537
    Both the revision history and the working directory will be
 
538
    updated.
 
539
 
 
540
    This command only works on branches that have not diverged.  Branches are
 
541
    considered diverged if both branches have had commits without first
 
542
    pulling from the other.
 
543
 
 
544
    If branches have diverged, you can use 'bzr merge' to pull the text changes
 
545
    from one into the other.
 
546
    """
 
547
    takes_args = ['location?']
 
548
 
 
549
    def run(self, location=None):
 
550
        from bzrlib.merge import merge
 
551
        import tempfile
 
552
        from shutil import rmtree
 
553
        import errno
 
554
        
 
555
        br_to = find_branch('.')
 
556
        stored_loc = None
 
557
        try:
 
558
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
 
559
        except IOError, e:
 
560
            if e.errno != errno.ENOENT:
 
561
                raise
 
562
        if location is None:
 
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
 
570
        br_from = find_branch(location)
 
571
        location = pull_loc(br_from)
 
572
        old_revno = br_to.revno()
 
573
        try:
 
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)
 
589
 
 
590
 
 
591
 
 
592
class cmd_branch(Command):
 
593
    """Create a new copy of a branch.
 
594
 
 
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".
 
600
    """
 
601
    takes_args = ['from_location', 'to_location?']
 
602
    takes_options = ['revision']
 
603
 
 
604
    def run(self, from_location, to_location=None, revision=None):
 
605
        import errno
 
606
        from bzrlib.merge import merge
 
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)
 
666
 
 
667
 
 
668
def pull_loc(branch):
 
669
    # TODO: Should perhaps just make attribute be 'base' in
 
670
    # RemoteBranch and Branch?
 
671
    if hasattr(branch, "baseurl"):
 
672
        return branch.baseurl
 
673
    else:
 
674
        return branch.base
 
675
 
 
676
 
 
677
 
386
678
class cmd_renames(Command):
387
679
    """Show list of renamed files.
388
680
 
393
685
    takes_args = ['dir?']
394
686
 
395
687
    def run(self, dir='.'):
396
 
        b = Branch(dir)
 
688
        b = find_branch(dir)
397
689
        old_inv = b.basis_tree().inventory
398
690
        new_inv = b.read_working_inventory()
399
691
 
410
702
    def run(self, branch=None):
411
703
        import info
412
704
 
413
 
        from branch import find_branch
414
705
        b = find_branch(branch)
415
706
        info.show_info(b)
416
707
 
425
716
    takes_options = ['verbose']
426
717
    
427
718
    def run(self, file_list, verbose=False):
428
 
        b = Branch(file_list[0])
 
719
        b = find_branch(file_list[0])
429
720
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
430
721
 
431
722
 
439
730
    hidden = True
440
731
    takes_args = ['filename']
441
732
    def run(self, filename):
442
 
        b = Branch(filename)
 
733
        b = find_branch(filename)
443
734
        i = b.inventory.path2id(b.relpath(filename))
444
735
        if i == None:
445
 
            bailout("%r is not a versioned file" % filename)
 
736
            raise BzrError("%r is not a versioned file" % filename)
446
737
        else:
447
738
            print i
448
739
 
455
746
    hidden = True
456
747
    takes_args = ['filename']
457
748
    def run(self, filename):
458
 
        b = Branch(filename)
 
749
        b = find_branch(filename)
459
750
        inv = b.inventory
460
751
        fid = inv.path2id(b.relpath(filename))
461
752
        if fid == None:
462
 
            bailout("%r is not a versioned file" % filename)
 
753
            raise BzrError("%r is not a versioned file" % filename)
463
754
        for fip in inv.get_idpath(fid):
464
755
            print fip
465
756
 
466
757
 
467
758
class cmd_revision_history(Command):
468
759
    """Display list of revision ids on this branch."""
 
760
    hidden = True
469
761
    def run(self):
470
 
        for patchid in Branch('.').revision_history():
 
762
        for patchid in find_branch('.').revision_history():
471
763
            print patchid
472
764
 
473
765
 
474
766
class cmd_directories(Command):
475
767
    """Display list of versioned directories in this branch."""
476
768
    def run(self):
477
 
        for name, ie in Branch('.').read_working_inventory().directories():
 
769
        for name, ie in find_branch('.').read_working_inventory().directories():
478
770
            if name == '':
479
771
                print '.'
480
772
            else:
495
787
        bzr commit -m 'imported project'
496
788
    """
497
789
    def run(self):
 
790
        from bzrlib.branch import Branch
498
791
        Branch('.', init=True)
499
792
 
500
793
 
524
817
    
525
818
    takes_args = ['file*']
526
819
    takes_options = ['revision', 'diff-options']
527
 
    aliases = ['di']
 
820
    aliases = ['di', 'dif']
528
821
 
529
822
    def run(self, revision=None, file_list=None, diff_options=None):
530
823
        from bzrlib.diff import show_diff
531
 
        from bzrlib import find_branch
532
824
 
533
825
        if file_list:
534
 
            b = find_branch(file_list[0], lock_mode='r')
 
826
            b = find_branch(file_list[0])
535
827
            file_list = [b.relpath(f) for f in file_list]
536
828
            if file_list == ['']:
537
829
                # just pointing to top-of-tree
538
830
                file_list = None
539
831
        else:
540
 
            b = Branch('.', lock_mode='r')
 
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]
541
840
    
542
 
        show_diff(b, revision, specific_files=file_list,
 
841
        show_diff(b, base_rev, specific_files=file_list,
543
842
                  external_diff_options=diff_options)
544
843
 
545
844
 
552
851
    TODO: Show files deleted since a previous revision, or between two revisions.
553
852
    """
554
853
    def run(self, show_ids=False):
555
 
        b = Branch('.')
 
854
        b = find_branch('.')
556
855
        old = b.basis_tree()
557
856
        new = b.working_tree()
558
857
 
573
872
    """List files modified in working tree."""
574
873
    hidden = True
575
874
    def run(self):
576
 
        import statcache
577
 
        b = Branch('.')
578
 
        inv = b.read_working_inventory()
579
 
        sc = statcache.update_cache(b, inv)
580
 
        basis = b.basis_tree()
581
 
        basis_inv = basis.inventory
582
 
        
583
 
        # We used to do this through iter_entries(), but that's slow
584
 
        # when most of the files are unmodified, as is usually the
585
 
        # case.  So instead we iterate by inventory entry, and only
586
 
        # calculate paths as necessary.
587
 
 
588
 
        for file_id in basis_inv:
589
 
            cacheentry = sc.get(file_id)
590
 
            if not cacheentry:                 # deleted
591
 
                continue
592
 
            ie = basis_inv[file_id]
593
 
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
594
 
                path = inv.id2path(file_id)
595
 
                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
596
882
 
597
883
 
598
884
 
600
886
    """List files added in working tree."""
601
887
    hidden = True
602
888
    def run(self):
603
 
        b = Branch('.')
 
889
        b = find_branch('.')
604
890
        wt = b.working_tree()
605
891
        basis_inv = b.basis_tree().inventory
606
892
        inv = wt.inventory
622
908
    takes_args = ['filename?']
623
909
    def run(self, filename=None):
624
910
        """Print the branch root."""
625
 
        from branch import find_branch
626
911
        b = find_branch(filename)
627
912
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
628
913
 
634
919
    -r revision requests a specific revision, -r :end or -r begin: are
635
920
    also valid.
636
921
 
 
922
    --message allows you to give a regular expression, which will be evaluated
 
923
    so that only matching entries will be displayed.
 
924
 
637
925
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
638
926
  
639
927
    """
640
928
 
641
929
    takes_args = ['filename?']
642
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
 
930
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision','long', 'message']
643
931
    
644
932
    def run(self, filename=None, timezone='original',
645
933
            verbose=False,
646
934
            show_ids=False,
647
935
            forward=False,
648
 
            revision=None):
649
 
        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
650
941
        import codecs
651
942
 
652
943
        direction = (forward and 'forward') or 'reverse'
653
944
        
654
945
        if filename:
655
 
            b = find_branch(filename, lock_mode='r')
 
946
            b = find_branch(filename)
656
947
            fp = b.relpath(filename)
657
948
            if fp:
658
949
                file_id = b.read_working_inventory().path2id(fp)
659
950
            else:
660
951
                file_id = None  # points to branch root
661
952
        else:
662
 
            b = find_branch('.', lock_mode='r')
 
953
            b = find_branch('.')
663
954
            file_id = None
664
955
 
665
 
        if revision == None:
666
 
            revision = [None, None]
667
 
        elif isinstance(revision, int):
668
 
            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]
669
964
        else:
670
 
            # pair of revisions?
671
 
            pass
672
 
            
673
 
        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
674
971
 
675
972
        mutter('encoding log as %r' % bzrlib.user_encoding)
676
 
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout)
677
 
 
678
 
        show_log(b, file_id,
679
 
                 show_timezone=timezone,
 
973
 
 
974
        # use 'replace' so that we don't abort if trying to write out
 
975
        # in e.g. the default C locale.
 
976
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
 
977
 
 
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,
680
990
                 verbose=verbose,
681
 
                 show_ids=show_ids,
682
 
                 to_file=outf,
683
991
                 direction=direction,
684
 
                 start_revision=revision[0],
685
 
                 end_revision=revision[1])
 
992
                 start_revision=rev1,
 
993
                 end_revision=rev2,
 
994
                 search=message)
686
995
 
687
996
 
688
997
 
693
1002
    hidden = True
694
1003
    takes_args = ["filename"]
695
1004
    def run(self, filename):
696
 
        b = Branch(filename, lock_mode='r')
 
1005
        b = find_branch(filename)
697
1006
        inv = b.read_working_inventory()
698
1007
        file_id = inv.path2id(b.relpath(filename))
699
1008
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
707
1016
    """
708
1017
    hidden = True
709
1018
    def run(self, revision=None, verbose=False):
710
 
        b = Branch('.')
 
1019
        b = find_branch('.')
711
1020
        if revision == None:
712
1021
            tree = b.working_tree()
713
1022
        else:
729
1038
 
730
1039
 
731
1040
class cmd_unknowns(Command):
732
 
    """List unknown files"""
 
1041
    """List unknown files."""
733
1042
    def run(self):
734
 
        for f in Branch('.').unknowns():
 
1043
        from bzrlib.osutils import quotefn
 
1044
        for f in find_branch('.').unknowns():
735
1045
            print quotefn(f)
736
1046
 
737
1047
 
738
1048
 
739
1049
class cmd_ignore(Command):
740
 
    """Ignore a command or pattern
 
1050
    """Ignore a command or pattern.
741
1051
 
742
1052
    To remove patterns from the ignore list, edit the .bzrignore file.
743
1053
 
759
1069
        from bzrlib.atomicfile import AtomicFile
760
1070
        import os.path
761
1071
 
762
 
        b = Branch('.')
 
1072
        b = find_branch('.')
763
1073
        ifn = b.abspath('.bzrignore')
764
1074
 
765
1075
        if os.path.exists(ifn):
799
1109
 
800
1110
    See also: bzr ignore"""
801
1111
    def run(self):
802
 
        tree = Branch('.').working_tree()
 
1112
        tree = find_branch('.').working_tree()
803
1113
        for path, file_class, kind, file_id in tree.list_files():
804
1114
            if file_class != 'I':
805
1115
                continue
823
1133
        except ValueError:
824
1134
            raise BzrCommandError("not a valid revision-number: %r" % revno)
825
1135
 
826
 
        print Branch('.').lookup_revision(revno)
 
1136
        print find_branch('.').lookup_revision(revno)
827
1137
 
828
1138
 
829
1139
class cmd_export(Command):
830
1140
    """Export past revision to destination directory.
831
1141
 
832
 
    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
833
1151
    takes_args = ['dest']
834
 
    takes_options = ['revision']
835
 
    def run(self, dest, revision=None):
836
 
        b = Branch('.')
837
 
        if revision == None:
838
 
            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()
839
1158
        else:
840
 
            rh = b.lookup_revision(int(revision))
841
 
        t = b.revision_tree(rh)
842
 
        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)
843
1174
 
844
1175
 
845
1176
class cmd_cat(Command):
851
1182
    def run(self, filename, revision=None):
852
1183
        if revision == None:
853
1184
            raise BzrCommandError("bzr cat requires a revision number")
854
 
        b = Branch('.')
855
 
        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])
856
1189
 
857
1190
 
858
1191
class cmd_local_time_offset(Command):
879
1212
    TODO: Strict commit that fails if there are unknown or deleted files.
880
1213
    """
881
1214
    takes_args = ['selected*']
882
 
    takes_options = ['message', 'file', 'verbose']
 
1215
    takes_options = ['message', 'file', 'verbose', 'unchanged']
883
1216
    aliases = ['ci', 'checkin']
884
1217
 
885
 
    def run(self, message=None, file=None, verbose=True, selected_list=None):
886
 
        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
887
1222
 
888
1223
        ## Warning: shadows builtin file()
889
1224
        if not message and not file:
890
 
            raise BzrCommandError("please specify a commit message",
891
 
                                  ["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"])
892
1237
        elif message and file:
893
1238
            raise BzrCommandError("please specify either --message or --file")
894
1239
        
896
1241
            import codecs
897
1242
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
898
1243
 
899
 
        b = Branch('.')
900
 
        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"])
901
1255
 
902
1256
 
903
1257
class cmd_check(Command):
905
1259
 
906
1260
    This command checks various invariants about the branch storage to
907
1261
    detect data corruption or bzr bugs.
908
 
    """
909
 
    takes_args = ['dir?']
910
 
    def run(self, dir='.'):
911
 
        import bzrlib.check
912
 
        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))
913
1306
 
914
1307
 
915
1308
 
927
1320
class cmd_selftest(Command):
928
1321
    """Run internal test suite"""
929
1322
    hidden = True
930
 
    def run(self):
931
 
        failures, tests = 0, 0
932
 
 
933
 
        import doctest, bzrlib.store
934
 
        bzrlib.trace.verbose = False
935
 
 
936
 
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
937
 
            bzrlib.tree, bzrlib.commands, bzrlib.add:
938
 
            mf, mt = doctest.testmod(m)
939
 
            failures += mf
940
 
            tests += mt
941
 
            print '%-40s %3d tests' % (m.__name__, mt),
942
 
            if mf:
943
 
                print '%3d FAILED!' % mf
944
 
            else:
945
 
                print
946
 
 
947
 
        print '%-40s %3d tests' % ('total', tests),
948
 
        if failures:
949
 
            print '%3d FAILED!' % failures
950
 
            return 1
951
 
        else:
952
 
            print
953
 
            return 0
954
 
 
 
1323
    takes_options = ['verbose']
 
1324
    def run(self, verbose=False):
 
1325
        from bzrlib.selftest import selftest
 
1326
        return int(not selftest(verbose=verbose))
955
1327
 
956
1328
 
957
1329
class cmd_version(Command):
958
 
    """Show version of bzr"""
 
1330
    """Show version of bzr."""
959
1331
    def run(self):
960
1332
        show_version()
961
1333
 
962
1334
def show_version():
963
1335
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
1336
    # is bzrlib itself in a branch?
 
1337
    bzrrev = bzrlib.get_bzr_revision()
 
1338
    if bzrrev:
 
1339
        print "  (bzr checkout, revision %d {%s})" % bzrrev
964
1340
    print bzrlib.__copyright__
965
1341
    print "http://bazaar-ng.org/"
966
1342
    print
976
1352
        print "it sure does!"
977
1353
 
978
1354
def parse_spec(spec):
 
1355
    """
 
1356
    >>> parse_spec(None)
 
1357
    [None, None]
 
1358
    >>> parse_spec("./")
 
1359
    ['./', None]
 
1360
    >>> parse_spec("../@")
 
1361
    ['..', -1]
 
1362
    >>> parse_spec("../f/@35")
 
1363
    ['../f', 35]
 
1364
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
1365
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
 
1366
    """
 
1367
    if spec is None:
 
1368
        return [None, None]
979
1369
    if '/@' in spec:
980
1370
        parsed = spec.split('/@')
981
1371
        assert len(parsed) == 2
982
1372
        if parsed[1] == "":
983
1373
            parsed[1] = -1
984
1374
        else:
985
 
            parsed[1] = int(parsed[1])
986
 
            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
987
1381
    else:
988
1382
        parsed = [spec, None]
989
1383
    return parsed
990
1384
 
 
1385
 
 
1386
 
991
1387
class cmd_merge(Command):
992
 
    """Perform a three-way merge of trees."""
993
 
    takes_args = ['other_spec', 'base_spec']
994
 
 
995
 
    def run(self, other_spec, base_spec):
996
 
        merge.merge(parse_spec(other_spec), parse_spec(base_spec))
 
1388
    """Perform a three-way merge of trees.
 
1389
    
 
1390
    The SPEC parameters are working tree or revision specifiers.  Working trees
 
1391
    are specified using standard paths or urls.  No component of a directory
 
1392
    path may begin with '@'.
 
1393
    
 
1394
    Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
 
1395
 
 
1396
    Revisions are specified using a dirname/@revno pair, where dirname is the
 
1397
    branch directory and revno is the revision within that branch.  If no revno
 
1398
    is specified, the latest revision is used.
 
1399
 
 
1400
    Revision examples: './@127', 'foo/@', '../@1'
 
1401
 
 
1402
    The OTHER_SPEC parameter is required.  If the BASE_SPEC parameter is
 
1403
    not supplied, the common ancestor of OTHER_SPEC the current branch is used
 
1404
    as the BASE.
 
1405
 
 
1406
    merge refuses to run if there are any uncommitted changes, unless
 
1407
    --force is given.
 
1408
    """
 
1409
    takes_args = ['other_spec', 'base_spec?']
 
1410
    takes_options = ['force', 'merge-type']
 
1411
 
 
1412
    def run(self, other_spec, base_spec=None, force=False, merge_type=None):
 
1413
        from bzrlib.merge import merge
 
1414
        from bzrlib.merge_core import ApplyMerge3
 
1415
        if merge_type is None:
 
1416
            merge_type = ApplyMerge3
 
1417
        merge(parse_spec(other_spec), parse_spec(base_spec),
 
1418
              check_clean=(not force), merge_type=merge_type)
 
1419
 
 
1420
 
 
1421
 
 
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):
 
1438
    """Reverse all changes since the last commit.
 
1439
 
 
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.
 
1442
    """
 
1443
    takes_options = ['revision', 'no-backup']
 
1444
 
 
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('.'),
 
1452
              check_clean=False,
 
1453
              ignore_zero=True,
 
1454
              backup_files=not no_backup)
 
1455
 
997
1456
 
998
1457
class cmd_assert_fail(Command):
999
1458
    """Test reporting of assertion failures"""
1014
1473
        help.help(topic)
1015
1474
 
1016
1475
 
1017
 
class cmd_update_stat_cache(Command):
1018
 
    """Update stat-cache mapping inodes to SHA-1 hashes.
1019
 
 
1020
 
    For testing only."""
 
1476
 
 
1477
 
 
1478
class cmd_plugins(Command):
 
1479
    """List plugins"""
1021
1480
    hidden = True
1022
1481
    def run(self):
1023
 
        import statcache
1024
 
        b = Branch('.')
1025
 
        statcache.update_cache(b.base, b.read_working_inventory())
1026
 
 
1027
 
 
1028
 
######################################################################
1029
 
# main routine
 
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)
 
1492
 
1030
1493
 
1031
1494
 
1032
1495
# list of all available options; the rhs can be either None for an
1037
1500
    'diff-options':           str,
1038
1501
    'help':                   None,
1039
1502
    'file':                   unicode,
 
1503
    'force':                  None,
 
1504
    'format':                 unicode,
1040
1505
    'forward':                None,
1041
1506
    'message':                unicode,
 
1507
    'no-recurse':             None,
1042
1508
    'profile':                None,
1043
1509
    'revision':               _parse_revision_str,
1044
1510
    'show-ids':               None,
1046
1512
    'verbose':                None,
1047
1513
    'version':                None,
1048
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,
1049
1521
    }
1050
1522
 
1051
1523
SHORT_OPTIONS = {
 
1524
    'F':                      'file', 
 
1525
    'h':                      'help',
1052
1526
    'm':                      'message',
1053
 
    'F':                      'file', 
1054
1527
    'r':                      'revision',
1055
1528
    'v':                      'verbose',
 
1529
    'l':                      'long',
1056
1530
}
1057
1531
 
1058
1532
 
1072
1546
    (['status'], {'all': True})
1073
1547
    >>> parse_args('commit --message=biter'.split())
1074
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]})
1075
1557
    """
1076
1558
    args = []
1077
1559
    opts = {}
1091
1573
                else:
1092
1574
                    optname = a[2:]
1093
1575
                if optname not in OPTIONS:
1094
 
                    bailout('unknown long option %r' % a)
 
1576
                    raise BzrError('unknown long option %r' % a)
1095
1577
            else:
1096
1578
                shortopt = a[1:]
1097
 
                if shortopt not in SHORT_OPTIONS:
1098
 
                    bailout('unknown short option %r' % a)
1099
 
                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:]
1100
1607
            
1101
1608
            if optname in opts:
1102
1609
                # XXX: Do we ever want to support this, e.g. for -r?
1103
 
                bailout('repeated option %r' % a)
 
1610
                raise BzrError('repeated option %r' % a)
1104
1611
                
1105
1612
            optargfn = OPTIONS[optname]
1106
1613
            if optargfn:
1107
1614
                if optarg == None:
1108
1615
                    if not argv:
1109
 
                        bailout('option %r needs an argument' % a)
 
1616
                        raise BzrError('option %r needs an argument' % a)
1110
1617
                    else:
1111
1618
                        optarg = argv.pop(0)
1112
1619
                opts[optname] = optargfn(optarg)
1113
1620
            else:
1114
1621
                if optarg != None:
1115
 
                    bailout('option %r takes no argument' % optname)
 
1622
                    raise BzrError('option %r takes no argument' % optname)
1116
1623
                opts[optname] = True
1117
1624
        else:
1118
1625
            args.append(a)
1166
1673
    return argdict
1167
1674
 
1168
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
 
1169
1743
 
1170
1744
def run_bzr(argv):
1171
1745
    """Execute a command.
1172
1746
 
1173
1747
    This is similar to main(), but without all the trappings for
1174
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.
1175
1754
    """
1176
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:]
1177
1780
    
1178
1781
    try:
1179
 
        args, opts = parse_args(argv[1:])
1180
 
        if 'help' in opts:
1181
 
            import help
1182
 
            if args:
1183
 
                help.help(args[0])
1184
 
            else:
1185
 
                help.help()
1186
 
            return 0
1187
 
        elif 'version' in opts:
1188
 
            show_version()
1189
 
            return 0
1190
1782
        cmd = str(args.pop(0))
1191
1783
    except IndexError:
1192
 
        import help
1193
 
        help.help()
 
1784
        print >>sys.stderr, "please try 'bzr help' for help"
1194
1785
        return 1
1195
 
          
1196
 
 
1197
 
    canonical_cmd, cmd_class = get_cmd_class(cmd)
1198
 
 
1199
 
    # 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
1200
1793
    if 'profile' in opts:
1201
1794
        profile = True
1202
1795
        del opts['profile']
1203
 
    else:
1204
 
        profile = False
1205
1796
 
1206
1797
    # check options are reasonable
1207
1798
    allowed = cmd_class.takes_options
1256
1847
 
1257
1848
 
1258
1849
def main(argv):
1259
 
    import errno
1260
1850
    
1261
 
    bzrlib.open_tracefile(argv)
 
1851
    bzrlib.trace.open_tracefile(argv)
1262
1852
 
1263
1853
    try:
1264
1854
        try:
1265
1855
            try:
1266
 
                return run_bzr(argv)
 
1856
                return run_bzr(argv[1:])
1267
1857
            finally:
1268
1858
                # do this here inside the exception wrappers to catch EPIPE
1269
1859
                sys.stdout.flush()
1285
1875
            _report_exception('interrupted', quiet=True)
1286
1876
            return 2
1287
1877
        except Exception, e:
 
1878
            import errno
1288
1879
            quiet = False
1289
1880
            if (isinstance(e, IOError) 
1290
1881
                and hasattr(e, 'errno')