~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-17 06:56:16 UTC
  • Revision ID: mbp@sourcefrog.net-20050517065616-6f23381d6184a8aa
- add space for un-merged patches

Show diffs side-by-side

added added

removed removed

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