~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzr.py

  • Committer: Martin Pool
  • Date: 2005-03-12 08:54:12 UTC
  • Revision ID: mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
doc: notes on implementing codeville-style merge on
top of a weave; looks nice but opens a can of worms

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python
 
2
 
 
3
 
1
4
# Copyright (C) 2004, 2005 by Martin Pool
2
5
# Copyright (C) 2005 by Canonical Ltd
3
6
 
25
28
* Metadata format is not stable yet -- you may need to
26
29
  discard history in the future.
27
30
 
 
31
* No handling of subdirectories, symlinks or any non-text files.
 
32
 
28
33
* Insufficient error handling.
29
34
 
30
35
* Many commands unimplemented or partially implemented.
35
40
 
36
41
Interesting commands::
37
42
 
38
 
  bzr help [COMMAND]
39
 
       Show help screen
 
43
  bzr help
 
44
       Show summary help screen
40
45
  bzr version
41
46
       Show software version/licence/non-warranty.
42
47
  bzr init
55
60
       Show summary of pending changes.
56
61
  bzr remove FILE...
57
62
       Make a file not versioned.
58
 
  bzr info
59
 
       Show statistics about this branch.
60
63
"""
61
64
 
62
 
 
 
65
# not currently working:
 
66
#  bzr check
 
67
#       Run internal consistency checks.
 
68
#  bzr info
 
69
#       Show some information about this branch.
 
70
 
 
71
 
 
72
 
 
73
__copyright__ = "Copyright 2005 Canonical Development Ltd."
 
74
__author__ = "Martin Pool <mbp@canonical.com>"
 
75
__docformat__ = "restructuredtext en"
 
76
__version__ = '0.0.0'
63
77
 
64
78
 
65
79
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
101
115
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
102
116
## to compare output?
103
117
 
104
 
## TODO: Some kind of global code to generate the right Branch object
105
 
## to work on.  Almost, but not quite all, commands need one, and it
106
 
## can be taken either from their parameters or their working
107
 
## directory.
108
 
 
109
 
## TODO: rename command, needed soon: check destination doesn't exist
110
 
## either in working copy or tree; move working copy; update
111
 
## inventory; write out
112
 
 
113
 
## TODO: move command; check destination is a directory and will not
114
 
## clash; move it.
115
 
 
116
 
## TODO: command to show renames, one per line, as to->from
117
 
 
118
 
 
 
118
 
 
119
 
 
120
 
 
121
######################################################################
 
122
# check status
119
123
 
120
124
 
121
125
def cmd_status(all=False):
137
141
    Branch('.').get_revision(revision_id).write_xml(sys.stdout)
138
142
 
139
143
 
 
144
def cmd_get_inventory(inventory_id):
 
145
    """Return inventory in XML by hash"""
 
146
    Branch('.').get_inventory(inventory_hash).write_xml(sys.stdout)
 
147
 
 
148
 
 
149
def cmd_get_revision_inventory(revision_id):
 
150
    """Output inventory for a revision."""
 
151
    b = Branch('.')
 
152
    b.get_revision_inventory(revision_id).write_xml(sys.stdout)
 
153
 
 
154
 
140
155
def cmd_get_file_text(text_id):
141
156
    """Get contents of a file by hash."""
142
157
    sf = Branch('.').text_store[text_id]
153
168
    print Branch('.').revno()
154
169
    
155
170
 
156
 
    
157
171
def cmd_add(file_list, verbose=False):
158
 
    """Add specified files or directories.
159
 
 
160
 
    In non-recursive mode, all the named items are added, regardless
161
 
    of whether they were previously ignored.  A warning is given if
162
 
    any of the named files are already versioned.
163
 
 
164
 
    In recursive mode (the default), files are treated the same way
165
 
    but the behaviour for directories is different.  Directories that
166
 
    are already versioned do not give a warning.  All directories,
167
 
    whether already versioned or not, are searched for files or
168
 
    subdirectories that are neither versioned or ignored, and these
169
 
    are added.  This search proceeds recursively into versioned
170
 
    directories.
171
 
 
172
 
    Therefore simply saying 'bzr add .' will version all files that
173
 
    are currently unknown.
 
172
    """Add specified files.
 
173
    
 
174
    Fails if the files are already added.
174
175
    """
175
 
    bzrlib.add.smart_add(file_list, verbose)
176
 
    
177
 
 
178
 
def cmd_relpath(filename):
179
 
    """Show path of file relative to root"""
180
 
    print Branch(filename).relpath(filename)
 
176
    Branch('.').add(file_list, verbose=verbose)
181
177
 
182
178
 
183
179
def cmd_inventory(revision=None):
196
192
 
197
193
 
198
194
def cmd_info():
199
 
    """info: Show statistical information for this branch
200
 
 
201
 
usage: bzr info"""
202
 
    import info
203
 
    info.show_info(Branch('.'))        
 
195
    b = Branch('.')
 
196
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
 
197
 
 
198
    def plural(n, base='', pl=None):
 
199
        if n == 1:
 
200
            return base
 
201
        elif pl is not None:
 
202
            return pl
 
203
        else:
 
204
            return 's'
 
205
 
 
206
    count_version_dirs = 0
 
207
 
 
208
    count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
 
209
    for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
 
210
        fs = st_tup[0]
 
211
        count_status[fs] += 1
 
212
        if fs not in ['I', '?'] and st_tup[4] == 'directory':
 
213
            count_version_dirs += 1
 
214
 
 
215
    print
 
216
    print 'in the working tree:'
 
217
    for name, fs in (('unchanged', '.'),
 
218
                     ('modified', 'M'), ('added', 'A'), ('removed', 'D'),
 
219
                     ('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
 
220
                     ):
 
221
        print '  %5d %s' % (count_status[fs], name)
 
222
    print '  %5d versioned subdirector%s' % (count_version_dirs,
 
223
                                             plural(count_version_dirs, 'y', 'ies'))
 
224
 
 
225
    print
 
226
    print 'branch history:'
 
227
    history = b.revision_history()
 
228
    revno = len(history)
 
229
    print '  %5d revision%s' % (revno, plural(revno))
 
230
    committers = Set()
 
231
    for rev in history:
 
232
        committers.add(b.get_revision(rev).committer)
 
233
    print '  %5d committer%s' % (len(committers), plural(len(committers)))
 
234
    if revno > 0:
 
235
        firstrev = b.get_revision(history[0])
 
236
        age = int((time.time() - firstrev.timestamp) / 3600 / 24)
 
237
        print '  %5d day%s old' % (age, plural(age))
 
238
        print '  first revision: %s' % format_date(firstrev.timestamp,
 
239
                                                 firstrev.timezone)
 
240
 
 
241
        lastrev = b.get_revision(history[-1])
 
242
        print '  latest revision: %s' % format_date(lastrev.timestamp,
 
243
                                                    lastrev.timezone)
 
244
        
204
245
    
205
246
 
206
247
 
207
248
def cmd_remove(file_list, verbose=False):
208
 
    b = Branch(file_list[0])
209
 
    b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
249
    Branch('.').remove(file_list, verbose=verbose)
210
250
 
211
251
 
212
252
 
213
253
def cmd_file_id(filename):
214
 
    b = Branch(filename)
215
 
    i = b.inventory.path2id(b.relpath(filename))
 
254
    i = Branch('.').read_working_inventory().path2id(filename)
216
255
    if i is None:
217
256
        bailout("%s is not a versioned file" % filename)
218
257
    else:
247
286
 
248
287
 
249
288
def cmd_diff(revision=None):
250
 
    """bzr diff: Show differences in working tree.
251
 
    
252
 
usage: bzr diff [-r REV]
253
 
 
254
 
--revision REV
255
 
    Show changes since REV, rather than predecessor.
256
 
 
257
 
TODO: Given two revision arguments, show the difference between them.
258
 
 
259
 
TODO: Allow diff across branches.
260
 
 
261
 
TODO: Option to use external diff command; could be GNU diff, wdiff,
262
 
or a graphical diff.
263
 
 
264
 
TODO: Diff selected files.
265
 
"""
266
 
 
267
 
    ## TODO: Shouldn't be in the cmd function.
 
289
    """Show diff from basis to working copy.
 
290
 
 
291
    :todo: Take one or two revision arguments, look up those trees,
 
292
           and diff them.
 
293
 
 
294
    :todo: Allow diff across branches.
 
295
 
 
296
    :todo: Mangle filenames in diff to be more relevant.
 
297
 
 
298
    :todo: Shouldn't be in the cmd function.
 
299
    """
268
300
 
269
301
    b = Branch('.')
270
302
 
339
371
 
340
372
 
341
373
 
342
 
def cmd_deleted(show_ids=False):
343
 
    """List files deleted in the working tree.
344
 
 
345
 
TODO: Show files deleted since a previous revision, or between two revisions.
346
 
    """
347
 
    b = Branch('.')
348
 
    old = b.basis_tree()
349
 
    new = b.working_tree()
350
 
 
351
 
    for path, ie in old.inventory.iter_entries():
352
 
        if not new.has_id(ie.file_id):
353
 
            if show_ids:
354
 
                print '%-50s %s' % (path, ie.file_id)
355
 
            else:
356
 
                print path
357
 
                
358
 
    
359
 
 
360
 
 
361
 
def cmd_root(filename=None):
362
 
    """Print the branch root."""
363
 
    print bzrlib.branch.find_branch_root(filename)
364
 
    
365
 
 
366
374
def cmd_log(timezone='original'):
367
375
    """Show log of this branch.
368
376
 
403
411
        print quotefn(f)
404
412
 
405
413
 
406
 
 
407
 
def cmd_ignored(verbose=True):
408
 
    """List ignored files and the patterns that matched them.
409
 
      """
410
 
    tree = Branch('.').working_tree()
411
 
    for path, file_class, kind, id in tree.list_files():
412
 
        if file_class != 'I':
413
 
            continue
414
 
        ## XXX: Slightly inefficient since this was already calculated
415
 
        pat = tree.is_ignored(path)
416
 
        print '%-50s %s' % (path, pat)
417
 
    
418
 
 
419
 
 
420
414
def cmd_lookup_revision(revno):
421
415
    try:
422
416
        revno = int(revno)
443
437
 
444
438
def cmd_uuid():
445
439
    """Print a newly-generated UUID."""
446
 
    print bzrlib.osutils.uuid()
 
440
    print uuid()
447
441
 
448
442
 
449
443
 
452
446
 
453
447
 
454
448
 
455
 
def cmd_commit(message=None, verbose=False):
456
 
    """Commit changes to a new revision.
457
 
 
458
 
--message MESSAGE
459
 
    Description of changes in this revision; free form text.
460
 
    It is recommended that the first line be a single-sentence
461
 
    summary.
462
 
--verbose
463
 
    Show status of changed files,
464
 
 
465
 
TODO: Commit only selected files.
466
 
 
467
 
TODO: Run hooks on tree to-be-committed, and after commit.
468
 
 
469
 
TODO: Strict commit that fails if there are unknown or deleted files.
470
 
"""
471
 
 
472
 
    if not message:
473
 
        bailout("please specify a commit message")
 
449
def cmd_commit(message, verbose=False):
474
450
    Branch('.').commit(message, verbose=verbose)
475
451
 
476
452
 
477
 
def cmd_check(dir='.'):
478
 
    """check: Consistency check of branch history.
479
 
 
480
 
usage: bzr check [-v] [BRANCH]
481
 
 
482
 
options:
483
 
  --verbose, -v         Show progress of checking.
484
 
 
485
 
This command checks various invariants about the branch storage to
486
 
detect data corruption or bzr bugs.
487
 
"""
488
 
    import bzrlib.check
489
 
    bzrlib.check.check(Branch(dir, find_root=False))
 
453
def cmd_check():
 
454
    """Check consistency of the branch."""
 
455
    check()
490
456
 
491
457
 
492
458
def cmd_is(pred, *rest):
517
483
    print bzrlib.branch._gen_revision_id(time.time())
518
484
 
519
485
 
520
 
def cmd_selftest(verbose=False):
521
 
    """Run internal test suite"""
 
486
def cmd_doctest():
 
487
    """Run internal doctest suite"""
522
488
    ## -v, if present, is seen by doctest; the argument is just here
523
489
    ## so our parser doesn't complain
524
490
 
525
491
    ## TODO: --verbose option
526
 
 
527
 
    failures, tests = 0, 0
528
492
    
529
 
    import doctest, bzrlib.store, bzrlib.tests
 
493
    import bzr, doctest, bzrlib.store
530
494
    bzrlib.trace.verbose = False
531
 
 
532
 
    for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
533
 
        bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
534
 
        mf, mt = doctest.testmod(m)
535
 
        failures += mf
536
 
        tests += mt
537
 
        print '%-40s %3d tests' % (m.__name__, mt),
538
 
        if mf:
539
 
            print '%3d FAILED!' % mf
540
 
        else:
541
 
            print
542
 
 
543
 
    print '%-40s %3d tests' % ('total', tests),
544
 
    if failures:
545
 
        print '%3d FAILED!' % failures
546
 
    else:
547
 
        print
548
 
 
549
 
 
550
 
 
551
 
# deprecated
552
 
cmd_doctest = cmd_selftest
 
495
    doctest.testmod(bzr)
 
496
    doctest.testmod(bzrlib.store)
 
497
    doctest.testmod(bzrlib.inventory)
 
498
    doctest.testmod(bzrlib.branch)
 
499
    doctest.testmod(bzrlib.osutils)
 
500
    doctest.testmod(bzrlib.tree)
 
501
 
 
502
    # more strenuous tests;
 
503
    import bzrlib.tests
 
504
    doctest.testmod(bzrlib.tests)
553
505
 
554
506
 
555
507
######################################################################
556
508
# help
557
509
 
558
510
 
559
 
def cmd_help(topic=None):
560
 
    if topic == None:
561
 
        print __doc__
562
 
        return
563
 
 
564
 
    # otherwise, maybe the name of a command?
565
 
    try:
566
 
        cmdfn = globals()['cmd_' + topic.replace('-', '_')]
567
 
    except KeyError:
568
 
        bailout("no help for %r" % topic)
569
 
 
570
 
    doc = cmdfn.__doc__
571
 
    if doc == None:
572
 
        bailout("sorry, no detailed help yet for %r" % topic)
573
 
 
574
 
    print doc
575
 
        
576
 
 
 
511
def cmd_help():
 
512
    # TODO: Specific help for particular commands
 
513
    print __doc__
577
514
 
578
515
 
579
516
def cmd_version():
580
 
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
581
 
    print bzrlib.__copyright__
 
517
    print "bzr (bazaar-ng) %s" % __version__
 
518
    print __copyright__
582
519
    print "http://bazaar-ng.org/"
583
520
    print
584
521
    print \
604
541
    'all':                    None,
605
542
    'help':                   None,
606
543
    'message':                unicode,
607
 
    'profile':                None,
608
544
    'revision':               int,
609
545
    'show-ids':               None,
610
546
    'timezone':               str,
623
559
cmd_options = {
624
560
    'add':                    ['verbose'],
625
561
    'commit':                 ['message', 'verbose'],
626
 
    'deleted':                ['show-ids'],
627
562
    'diff':                   ['revision'],
628
563
    'inventory':              ['revision'],
629
 
    'log':                    ['timezone'],
 
564
    'log':                    ['show-ids', 'timezone'],
630
565
    'ls':                     ['revision', 'verbose'],
631
566
    'remove':                 ['verbose'],
632
567
    'status':                 ['all'],
634
569
 
635
570
 
636
571
cmd_args = {
 
572
    'init':                   [],
637
573
    'add':                    ['file+'],
638
574
    'commit':                 [],
639
575
    'diff':                   [],
640
 
    'export':                 ['revno', 'dest'],
641
576
    'file-id':                ['filename'],
642
577
    'get-file-text':          ['text_id'],
643
578
    'get-inventory':          ['inventory_id'],
644
579
    'get-revision':           ['revision_id'],
645
580
    'get-revision-inventory': ['revision_id'],
646
 
    'help':                   ['topic?'],
647
 
    'init':                   [],
648
581
    'log':                    [],
649
582
    'lookup-revision':        ['revno'],
650
 
    'relpath':                ['filename'],
 
583
    'export':                 ['revno', 'dest'],
651
584
    'remove':                 ['file+'],
652
 
    'root':                   ['filename?'],
653
585
    'status':                 [],
654
586
    }
655
587
 
662
594
    lookup table, something about the available options, what optargs
663
595
    they take, and which commands will accept them.
664
596
 
665
 
    >>> parse_args('--help'.split())
 
597
    >>> parse_args('bzr --help'.split())
666
598
    ([], {'help': True})
667
 
    >>> parse_args('--version'.split())
 
599
    >>> parse_args('bzr --version'.split())
668
600
    ([], {'version': True})
669
 
    >>> parse_args('status --all'.split())
 
601
    >>> parse_args('bzr status --all'.split())
670
602
    (['status'], {'all': True})
671
 
    >>> parse_args('commit --message=biter'.split())
 
603
    >>> parse_args('bzr commit --message=biter'.split())
672
604
    (['commit'], {'message': u'biter'})
673
605
    """
674
606
    args = []
706
638
                    else:
707
639
                        optarg = argv.pop(0)
708
640
                opts[optname] = optargfn(optarg)
 
641
                mutter("    option argument %r" % opts[optname])
709
642
            else:
710
643
                if optarg != None:
711
644
                    bailout('option %r takes no argument' % optname)
735
668
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
736
669
    # all but one.
737
670
 
738
 
    # step through args and argform, allowing appropriate 0-many matches
739
671
    for ap in argform:
740
672
        argname = ap[:-1]
741
673
        if ap[-1] == '?':
742
 
            if args:
743
 
                argdict[argname] = args.pop(0)
 
674
            assert 0
744
675
        elif ap[-1] == '*':
745
676
            assert 0
746
677
        elif ap[-1] == '+':
788
719
        log_error('usage: bzr COMMAND\n')
789
720
        log_error('  try "bzr help"\n')
790
721
        return 1
791
 
 
 
722
            
792
723
    try:
793
724
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
794
725
    except KeyError:
795
726
        bailout("unknown command " + `cmd`)
796
727
 
797
 
    # global option
798
 
    if 'profile' in opts:
799
 
        profile = True
800
 
        del opts['profile']
801
 
    else:
802
 
        profile = False
 
728
    # TODO: special --profile option to turn on the Python profiler
803
729
 
804
730
    # check options are reasonable
805
731
    allowed = cmd_options.get(cmd, [])
808
734
            bailout("option %r is not allowed for command %r"
809
735
                    % (oname, cmd))
810
736
 
811
 
    # mix arguments and options into one dictionary
812
737
    cmdargs = _match_args(cmd, args)
813
 
    for k, v in opts.items():
814
 
        cmdargs[k.replace('-', '_')] = v
815
 
 
816
 
    if profile:
817
 
        import hotshot
818
 
        prof = hotshot.Profile('.bzr.profile')
819
 
        ret = prof.runcall(cmd_handler, **cmdargs) or 0
820
 
        prof.close()
821
 
 
822
 
        import hotshot.stats
823
 
        stats = hotshot.stats.load('.bzr.profile')
824
 
        #stats.strip_dirs()
825
 
        stats.sort_stats('cumulative', 'calls')
826
 
        stats.print_stats(20)
827
 
    else:
828
 
        return cmd_handler(**cmdargs) or 0
 
738
    cmdargs.update(opts)
 
739
 
 
740
    ret = cmd_handler(**cmdargs) or 0
829
741
 
830
742
 
831
743
 
836
748
    ## TODO: If the arguments are wrong, give a usage message rather
837
749
    ## than just a backtrace.
838
750
 
839
 
    bzrlib.trace.create_tracefile(argv)
840
 
    
841
751
    try:
 
752
        t = bzrlib.trace._tracefile
 
753
        t.write('-' * 60 + '\n')
 
754
        t.write('bzr invoked at %s\n' % format_date(time.time()))
 
755
        t.write('  by %s on %s\n' % (bzrlib.osutils.username(), socket.gethostname()))
 
756
        t.write('  arguments: %r\n' % argv)
 
757
 
 
758
        starttime = os.times()[4]
 
759
 
 
760
        import platform
 
761
        t.write('  platform: %s\n' % platform.platform())
 
762
        t.write('  python: %s\n' % platform.python_version())
 
763
 
842
764
        ret = run_bzr(argv)
 
765
        
 
766
        times = os.times()
 
767
        mutter("finished, %.3fu/%.3fs cpu, %.3fu/%.3fs cum"
 
768
               % times[:4])
 
769
        mutter("    %.3f elapsed" % (times[4] - starttime))
 
770
 
843
771
        return ret
844
772
    except BzrError, e:
845
773
        log_error('bzr: error: ' + e.args[0] + '\n')