~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-10 08:15:58 UTC
  • Revision ID: mbp@sourcefrog.net-20050510081558-9a38e2c46ba4ebc4
- Patch from Fredrik Lundh to check Python version and 
  try to find a better one if it's too old.

  Patched to try to prevent infinite loops in wierd configurations,
  and to log to stderr.

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
24
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
24
 
from bzrlib.osutils import quotefn
25
 
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
 
25
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
26
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
27
from bzrlib.revision import Revision
 
28
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
26
29
     format_date
27
30
 
28
31
 
34
37
    assert cmd.startswith("cmd_")
35
38
    return cmd[4:].replace('_','-')
36
39
 
37
 
def _parse_revision_str(revstr):
38
 
    """This handles a revision string -> revno. 
39
 
 
40
 
    There are several possibilities:
41
 
 
42
 
        '234'       -> 234
43
 
        '234:345'   -> [234, 345]
44
 
        ':234'      -> [None, 234]
45
 
        '234:'      -> [234, None]
46
 
 
47
 
    In the future we will also support:
48
 
        'uuid:blah-blah-blah'   -> ?
49
 
        'hash:blahblahblah'     -> ?
50
 
        potentially:
51
 
        'tag:mytag'             -> ?
52
 
    """
53
 
    if revstr.find(':') != -1:
54
 
        revs = revstr.split(':')
55
 
        if len(revs) > 2:
56
 
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
57
 
 
58
 
        if not revs[0]:
59
 
            revs[0] = None
60
 
        else:
61
 
            revs[0] = int(revs[0])
62
 
 
63
 
        if not revs[1]:
64
 
            revs[1] = None
65
 
        else:
66
 
            revs[1] = int(revs[1])
67
 
    else:
68
 
        revs = int(revstr)
69
 
    return revs
70
 
 
71
40
def get_all_cmds():
72
41
    """Return canonical name and class for all registered commands."""
73
42
    for k, v in globals().iteritems():
97
66
    raise BzrCommandError("unknown command %r" % cmd)
98
67
 
99
68
 
100
 
class Command(object):
 
69
class Command:
101
70
    """Base class for commands.
102
71
 
103
72
    The docstring for an actual command should give a single-line
162
131
    """
163
132
 
164
133
    def find_command(cls, cmd):
165
 
        import os.path
166
134
        bzrpath = os.environ.get('BZRPATH', '')
167
135
 
168
136
        for dir in bzrpath.split(':'):
221
189
class cmd_status(Command):
222
190
    """Display status summary.
223
191
 
224
 
    This reports on versioned and unknown files, reporting them
225
 
    grouped by state.  Possible states are:
226
 
 
227
 
    added
228
 
        Versioned in the working copy but not in the previous revision.
229
 
 
230
 
    removed
231
 
        Versioned in the previous revision but removed or deleted
232
 
        in the working copy.
233
 
 
234
 
    renamed
235
 
        Path of this file changed from the previous revision;
236
 
        the text may also have changed.  This includes files whose
237
 
        parent directory was renamed.
238
 
 
239
 
    modified
240
 
        Text has changed since the previous revision.
241
 
 
242
 
    unchanged
243
 
        Nothing about this file has changed since the previous revision.
244
 
        Only shown with --all.
245
 
 
246
 
    unknown
247
 
        Not versioned and not matching an ignore pattern.
248
 
 
249
 
    To see ignored files use 'bzr ignored'.  For details in the
250
 
    changes to file texts, use 'bzr diff'.
251
 
 
252
 
    If no arguments are specified, the status of the entire working
253
 
    directory is shown.  Otherwise, only the status of the specified
254
 
    files or directories is reported.  If a directory is given, status
255
 
    is reported for everything inside that directory.
 
192
    For each file there is a single line giving its file state and name.
 
193
    The name is that in the current revision unless it is deleted or
 
194
    missing, in which case the old name is shown.
256
195
    """
257
196
    takes_args = ['file*']
258
 
    takes_options = ['all', 'show-ids']
 
197
    takes_options = ['all']
259
198
    aliases = ['st', 'stat']
260
199
    
261
 
    def run(self, all=False, show_ids=False, file_list=None):
262
 
        if file_list:
263
 
            b = Branch(file_list[0])
264
 
            file_list = [b.relpath(x) for x in file_list]
265
 
            # special case: only one path was given and it's the root
266
 
            # of the branch
267
 
            if file_list == ['']:
268
 
                file_list = None
269
 
        else:
270
 
            b = Branch('.')
271
 
        import status
272
 
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
273
 
                           specific_files=file_list)
 
200
    def run(self, all=False, file_list=None):
 
201
        b = Branch('.', lock_mode='r')
 
202
        b.show_status(show_all=all, file_list=file_list)
274
203
 
275
204
 
276
205
class cmd_cat_revision(Command):
322
251
class cmd_relpath(Command):
323
252
    """Show path of a file relative to root"""
324
253
    takes_args = ['filename']
325
 
    hidden = True
326
254
    
327
255
    def run(self, filename):
328
256
        print Branch(filename).relpath(filename)
331
259
 
332
260
class cmd_inventory(Command):
333
261
    """Show inventory of the current working copy or a revision."""
334
 
    takes_options = ['revision', 'show-ids']
 
262
    takes_options = ['revision']
335
263
    
336
 
    def run(self, revision=None, show_ids=False):
 
264
    def run(self, revision=None):
337
265
        b = Branch('.')
338
266
        if revision == None:
339
267
            inv = b.read_working_inventory()
340
268
        else:
341
269
            inv = b.get_revision_inventory(b.lookup_revision(revision))
342
270
 
343
 
        for path, entry in inv.entries():
344
 
            if show_ids:
345
 
                print '%-50s %s' % (path, entry.file_id)
346
 
            else:
347
 
                print path
 
271
        for path, entry in inv.iter_entries():
 
272
            print '%-50s %s' % (entry.file_id, path)
348
273
 
349
274
 
350
275
class cmd_move(Command):
405
330
 
406
331
 
407
332
class cmd_info(Command):
408
 
    """Show statistical information about a branch."""
409
 
    takes_args = ['branch?']
410
 
    
411
 
    def run(self, branch=None):
 
333
    """Show statistical information for this branch"""
 
334
    def run(self):
412
335
        import info
413
 
 
414
 
        from branch import find_branch
415
 
        b = find_branch(branch)
416
 
        info.show_info(b)
 
336
        info.show_info(Branch('.'))        
417
337
 
418
338
 
419
339
class cmd_remove(Command):
467
387
 
468
388
class cmd_revision_history(Command):
469
389
    """Display list of revision ids on this branch."""
470
 
    hidden = True
471
390
    def run(self):
472
391
        for patchid in Branch('.').revision_history():
473
392
            print patchid
525
444
    """
526
445
    
527
446
    takes_args = ['file*']
528
 
    takes_options = ['revision', 'diff-options']
 
447
    takes_options = ['revision']
529
448
    aliases = ['di']
530
449
 
531
 
    def run(self, revision=None, file_list=None, diff_options=None):
 
450
    def run(self, revision=None, file_list=None):
532
451
        from bzrlib.diff import show_diff
533
 
        from bzrlib import find_branch
534
 
 
535
 
        if file_list:
536
 
            b = find_branch(file_list[0])
537
 
            file_list = [b.relpath(f) for f in file_list]
538
 
            if file_list == ['']:
539
 
                # just pointing to top-of-tree
540
 
                file_list = None
541
 
        else:
542
 
            b = Branch('.')
543
452
    
544
 
        show_diff(b, revision, specific_files=file_list,
545
 
                  external_diff_options=diff_options)
 
453
        show_diff(Branch('.'), revision, file_list)
546
454
 
547
455
 
548
456
        
632
540
class cmd_log(Command):
633
541
    """Show log of this branch.
634
542
 
635
 
    To request a range of logs, you can use the command -r begin:end
636
 
    -r revision requests a specific revision, -r :end or -r begin: are
637
 
    also valid.
 
543
    TODO: Option to limit range.
638
544
 
639
 
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
640
 
  
 
545
    TODO: Perhaps show most-recent first with an option for last.
641
546
    """
642
 
 
643
547
    takes_args = ['filename?']
644
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
645
 
    
646
 
    def run(self, filename=None, timezone='original',
647
 
            verbose=False,
648
 
            show_ids=False,
649
 
            forward=False,
650
 
            revision=None):
651
 
        from bzrlib import show_log, find_branch
652
 
        import codecs
653
 
 
654
 
        direction = (forward and 'forward') or 'reverse'
655
 
        
 
548
    takes_options = ['timezone', 'verbose', 'show-ids']
 
549
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
 
550
        from branch import find_branch
 
551
        b = find_branch((filename or '.'), lock_mode='r')
656
552
        if filename:
657
 
            b = find_branch(filename)
658
 
            fp = b.relpath(filename)
659
 
            if fp:
660
 
                file_id = b.read_working_inventory().path2id(fp)
661
 
            else:
662
 
                file_id = None  # points to branch root
663
 
        else:
664
 
            b = find_branch('.')
665
 
            file_id = None
666
 
 
667
 
        if revision == None:
668
 
            revision = [None, None]
669
 
        elif isinstance(revision, int):
670
 
            revision = [revision, revision]
671
 
        else:
672
 
            # pair of revisions?
673
 
            pass
674
 
            
675
 
        assert len(revision) == 2
676
 
 
677
 
        mutter('encoding log as %r' % bzrlib.user_encoding)
678
 
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout)
679
 
 
680
 
        show_log(b, file_id,
681
 
                 show_timezone=timezone,
682
 
                 verbose=verbose,
683
 
                 show_ids=show_ids,
684
 
                 to_file=outf,
685
 
                 direction=direction,
686
 
                 start_revision=revision[0],
687
 
                 end_revision=revision[1])
 
553
            filename = b.relpath(filename)
 
554
        bzrlib.show_log(b, filename,
 
555
                        show_timezone=timezone,
 
556
                        verbose=verbose,
 
557
                        show_ids=show_ids)
688
558
 
689
559
 
690
560
 
691
561
class cmd_touching_revisions(Command):
692
 
    """Return revision-ids which affected a particular file.
693
 
 
694
 
    A more user-friendly interface is "bzr log FILE"."""
 
562
    """Return revision-ids which affected a particular file."""
695
563
    hidden = True
696
564
    takes_args = ["filename"]
697
565
    def run(self, filename):
698
 
        b = Branch(filename)
 
566
        b = Branch(filename, lock_mode='r')
699
567
        inv = b.read_working_inventory()
700
568
        file_id = inv.path2id(b.relpath(filename))
701
569
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
759
627
    
760
628
    def run(self, name_pattern):
761
629
        from bzrlib.atomicfile import AtomicFile
762
 
        import os.path
 
630
        import codecs
763
631
 
764
632
        b = Branch('.')
765
633
        ifn = b.abspath('.bzrignore')
766
634
 
 
635
        # FIXME: probably doesn't handle non-ascii patterns
 
636
 
767
637
        if os.path.exists(ifn):
768
 
            f = open(ifn, 'rt')
769
 
            try:
770
 
                igns = f.read().decode('utf-8')
771
 
            finally:
772
 
                f.close()
 
638
            f = b.controlfile(ifn, 'rt')
 
639
            igns = f.read()
 
640
            f.close()
773
641
        else:
774
642
            igns = ''
775
643
 
776
 
        # TODO: If the file already uses crlf-style termination, maybe
777
 
        # we should use that for the newly added lines?
778
 
 
779
644
        if igns and igns[-1] != '\n':
780
645
            igns += '\n'
781
646
        igns += name_pattern + '\n'
782
647
 
783
 
        try:
784
 
            f = AtomicFile(ifn, 'wt')
785
 
            f.write(igns.encode('utf-8'))
786
 
            f.commit()
787
 
        finally:
788
 
            f.close()
 
648
        f = AtomicFile(ifn, 'wt')
 
649
        f.write(igns)
 
650
        f.commit()
789
651
 
790
652
        inv = b.working_tree().inventory
791
653
        if inv.path2id('.bzrignore'):
868
730
class cmd_commit(Command):
869
731
    """Commit changes into a new revision.
870
732
 
871
 
    If selected files are specified, only changes to those files are
872
 
    committed.  If a directory is specified then its contents are also
873
 
    committed.
874
 
 
875
 
    A selected-file commit may fail in some cases where the committed
876
 
    tree would be invalid, such as trying to commit a file in a
877
 
    newly-added directory that is not itself committed.
 
733
    TODO: Commit only selected files.
878
734
 
879
735
    TODO: Run hooks on tree to-be-committed, and after commit.
880
736
 
881
737
    TODO: Strict commit that fails if there are unknown or deleted files.
882
738
    """
883
 
    takes_args = ['selected*']
884
739
    takes_options = ['message', 'file', 'verbose']
885
740
    aliases = ['ci', 'checkin']
886
741
 
887
 
    def run(self, message=None, file=None, verbose=True, selected_list=None):
888
 
        from bzrlib.commit import commit
889
 
 
 
742
    def run(self, message=None, file=None, verbose=False):
890
743
        ## Warning: shadows builtin file()
891
744
        if not message and not file:
892
745
            raise BzrCommandError("please specify a commit message",
898
751
            import codecs
899
752
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
900
753
 
901
 
        b = Branch('.')
902
 
        commit(b, message, verbose=verbose, specific_files=selected_list)
 
754
        Branch('.').commit(message, verbose=verbose)
903
755
 
904
756
 
905
757
class cmd_check(Command):
911
763
    takes_args = ['dir?']
912
764
    def run(self, dir='.'):
913
765
        import bzrlib.check
914
 
        bzrlib.check.check(Branch(dir))
 
766
        bzrlib.check.check(Branch(dir, find_root=False))
915
767
 
916
768
 
917
769
 
932
784
    def run(self):
933
785
        failures, tests = 0, 0
934
786
 
935
 
        import doctest, bzrlib.store
 
787
        import doctest, bzrlib.store, bzrlib.tests
936
788
        bzrlib.trace.verbose = False
937
789
 
938
790
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
939
 
            bzrlib.tree, bzrlib.commands, bzrlib.add:
 
791
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
940
792
            mf, mt = doctest.testmod(m)
941
793
            failures += mf
942
794
            tests += mt
949
801
        print '%-40s %3d tests' % ('total', tests),
950
802
        if failures:
951
803
            print '%3d FAILED!' % failures
952
 
            return 1
953
804
        else:
954
805
            print
955
 
            return 0
956
806
 
957
807
 
958
808
 
977
827
    def run(self):
978
828
        print "it sure does!"
979
829
 
980
 
def parse_spec(spec):
981
 
    if '/@' in spec:
982
 
        parsed = spec.split('/@')
983
 
        assert len(parsed) == 2
984
 
        if parsed[1] == "":
985
 
            parsed[1] = -1
986
 
        else:
987
 
            parsed[1] = int(parsed[1])
988
 
            assert parsed[1] >=0
989
 
    else:
990
 
        parsed = [spec, None]
991
 
    return parsed
992
 
 
993
 
class cmd_merge(Command):
994
 
    """Perform a three-way merge of trees."""
995
 
    takes_args = ['other_spec', 'base_spec']
996
 
 
997
 
    def run(self, other_spec, base_spec):
998
 
        from bzrlib.merge import merge
999
 
        merge(parse_spec(other_spec), parse_spec(base_spec))
1000
830
 
1001
831
class cmd_assert_fail(Command):
1002
832
    """Test reporting of assertion failures"""
1025
855
    def run(self):
1026
856
        import statcache
1027
857
        b = Branch('.')
1028
 
        statcache.update_cache(b.base, b.read_working_inventory())
1029
 
 
 
858
        statcache.update_cache(b)
 
859
 
 
860
 
 
861
######################################################################
 
862
# main routine
1030
863
 
1031
864
 
1032
865
# list of all available options; the rhs can be either None for an
1034
867
# the type.
1035
868
OPTIONS = {
1036
869
    'all':                    None,
1037
 
    'diff-options':           str,
1038
870
    'help':                   None,
1039
871
    'file':                   unicode,
1040
 
    'forward':                None,
1041
872
    'message':                unicode,
1042
873
    'profile':                None,
1043
 
    'revision':               _parse_revision_str,
 
874
    'revision':               int,
1044
875
    'show-ids':               None,
1045
876
    'timezone':               str,
1046
877
    'verbose':                None,
1049
880
    }
1050
881
 
1051
882
SHORT_OPTIONS = {
 
883
    'm':                      'message',
1052
884
    'F':                      'file', 
1053
 
    'h':                      'help',
1054
 
    'm':                      'message',
1055
885
    'r':                      'revision',
1056
886
    'v':                      'verbose',
1057
887
}
1190
1020
            return 0
1191
1021
        cmd = str(args.pop(0))
1192
1022
    except IndexError:
1193
 
        import help
1194
 
        help.help()
 
1023
        log_error('usage: bzr COMMAND')
 
1024
        log_error('  try "bzr help"')
1195
1025
        return 1
1196
 
          
1197
1026
 
1198
1027
    canonical_cmd, cmd_class = get_cmd_class(cmd)
1199
1028
 
1239
1068
            os.close(pffileno)
1240
1069
            os.remove(pfname)
1241
1070
    else:
1242
 
        return cmd_class(cmdopts, cmdargs).status 
 
1071
        cmdobj = cmd_class(cmdopts, cmdargs).status 
1243
1072
 
1244
1073
 
1245
1074
def _report_exception(summary, quiet=False):