/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzr.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-11 06:24:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050311062440-fd4ec249b6b139ab
don't abort on trees that happen to contain symlinks
(they still can't be versioned though).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python
 
2
 
 
3
 
 
4
# Copyright (C) 2004, 2005 by Martin Pool
 
5
# Copyright (C) 2005 by Canonical Ltd
 
6
 
 
7
 
 
8
# This program is free software; you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation; either version 2 of the License, or
 
11
# (at your option) any later version.
 
12
 
 
13
# This program is distributed in the hope that it will be useful,
 
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
# GNU General Public License for more details.
 
17
 
 
18
# You should have received a copy of the GNU General Public License
 
19
# along with this program; if not, write to the Free Software
 
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
21
 
 
22
"""Bazaar-NG -- a free distributed version-control tool
 
23
 
 
24
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
 
25
 
 
26
Current limitation include:
 
27
 
 
28
* Metadata format is not stable yet -- you may need to
 
29
  discard history in the future.
 
30
 
 
31
* No handling of subdirectories, symlinks or any non-text files.
 
32
 
 
33
* Insufficient error handling.
 
34
 
 
35
* Many commands unimplemented or partially implemented.
 
36
 
 
37
* Space-inefficient storage.
 
38
 
 
39
* No merge operators yet.
 
40
 
 
41
Interesting commands::
 
42
 
 
43
  bzr help
 
44
       Show summary help screen
 
45
  bzr version
 
46
       Show software version/licence/non-warranty.
 
47
  bzr init
 
48
       Start versioning the current directory
 
49
  bzr add FILE...
 
50
       Make files versioned.
 
51
  bzr log
 
52
       Show revision history.
 
53
  bzr diff
 
54
       Show changes from last revision to working copy.
 
55
  bzr commit -m 'MESSAGE'
 
56
       Store current state as new revision.
 
57
  bzr export REVNO DESTINATION
 
58
       Export the branch state at a previous version.
 
59
  bzr status
 
60
       Show summary of pending changes.
 
61
  bzr remove FILE...
 
62
       Make a file not versioned.
 
63
"""
 
64
 
 
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'
 
77
 
 
78
 
 
79
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
 
80
import traceback, socket, fnmatch, difflib
 
81
from os import path
 
82
from sets import Set
 
83
from pprint import pprint
 
84
from stat import *
 
85
from glob import glob
 
86
 
 
87
import bzrlib
 
88
from bzrlib.store import ImmutableStore
 
89
from bzrlib.trace import mutter, note, log_error
 
90
from bzrlib.errors import bailout, BzrError
 
91
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
92
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
93
from bzrlib.revision import Revision
 
94
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
95
     format_date
 
96
 
 
97
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
 
98
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
 
99
 
 
100
## standard representation
 
101
NONE_STRING = '(none)'
 
102
EMPTY = 'empty'
 
103
 
 
104
 
 
105
## TODO: Perhaps a different version of inventory commands that
 
106
## returns iterators...
 
107
 
 
108
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
 
109
 
 
110
## TODO: Some kind of locking on branches.  Perhaps there should be a
 
111
## parameter to the branch object saying whether we want a read or
 
112
## write lock; release it from destructor.  Perhaps don't even need a
 
113
## read lock to look at immutable objects?
 
114
 
 
115
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
 
116
## to compare output?
 
117
 
 
118
 
 
119
 
 
120
 
 
121
######################################################################
 
122
# check status
 
123
 
 
124
 
 
125
def cmd_status(all=False):
 
126
    """Display status summary.
 
127
 
 
128
    For each file there is a single line giving its file state and name.
 
129
    The name is that in the current revision unless it is deleted or
 
130
    missing, in which case the old name is shown.
 
131
 
 
132
    :todo: Don't show unchanged files unless ``--all`` is given?
 
133
    """
 
134
    Branch('.').show_status(show_all=all)
 
135
 
 
136
 
 
137
 
 
138
######################################################################
 
139
# examining history
 
140
def cmd_get_revision(revision_id):
 
141
    Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
142
 
 
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
 
 
155
def cmd_get_file_text(text_id):
 
156
    """Get contents of a file by hash."""
 
157
    sf = Branch('.').text_store[text_id]
 
158
    pumpfile(sf, sys.stdout)
 
159
 
 
160
 
 
161
 
 
162
######################################################################
 
163
# commands
 
164
    
 
165
 
 
166
def cmd_revno():
 
167
    """Show number of revisions on this branch"""
 
168
    print Branch('.').revno()
 
169
    
 
170
 
 
171
def cmd_add(file_list, verbose=False):
 
172
    """Add specified files.
 
173
    
 
174
    Fails if the files are already added.
 
175
    """
 
176
    Branch('.').add(file_list, verbose=verbose)
 
177
 
 
178
 
 
179
def cmd_inventory(revision=None):
 
180
    """Show inventory of the current working copy."""
 
181
    ## TODO: Also optionally show a previous inventory
 
182
    ## TODO: Format options
 
183
    b = Branch('.')
 
184
    if revision == None:
 
185
        inv = b.read_working_inventory()
 
186
    else:
 
187
        inv = b.get_revision_inventory(b.lookup_revision(revision))
 
188
        
 
189
    for path, entry in inv.iter_entries():
 
190
        print '%-50s %s' % (entry.file_id, path)
 
191
 
 
192
 
 
193
 
 
194
def cmd_info():
 
195
    b = Branch('.')
 
196
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
 
197
    print 'revision number:', b.revno()
 
198
 
 
199
    count_version_dirs = 0
 
200
 
 
201
    count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
 
202
    for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
 
203
        fs = st_tup[0]
 
204
        count_status[fs] += 1
 
205
        if fs not in ['I', '?'] and st_tup[4] == 'directory':
 
206
            count_version_dirs += 1
 
207
 
 
208
    print
 
209
    print 'in the working tree:'
 
210
    for name, fs in (('unchanged', '.'),
 
211
                     ('modified', 'M'), ('added', 'A'), ('removed', 'D'),
 
212
                     ('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
 
213
                     ):
 
214
        print '  %5d %s' % (count_status[fs], name)
 
215
    print '  %5d versioned subdirectories' % count_version_dirs
 
216
            
 
217
 
 
218
 
 
219
def cmd_remove(file_list, verbose=False):
 
220
    Branch('.').remove(file_list, verbose=verbose)
 
221
 
 
222
 
 
223
 
 
224
def cmd_file_id(filename):
 
225
    i = Branch('.').read_working_inventory().path2id(filename)
 
226
    if i is None:
 
227
        bailout("%s is not a versioned file" % filename)
 
228
    else:
 
229
        print i
 
230
 
 
231
 
 
232
def cmd_find_filename(fileid):
 
233
    n = find_filename(fileid)
 
234
    if n is None:
 
235
        bailout("%s is not a live file id" % fileid)
 
236
    else:
 
237
        print n
 
238
 
 
239
 
 
240
def cmd_revision_history():
 
241
    for patchid in Branch('.').revision_history():
 
242
        print patchid
 
243
 
 
244
 
 
245
 
 
246
def cmd_init():
 
247
    # TODO: Check we're not already in a working directory?  At the
 
248
    # moment you'll get an ugly error.
 
249
    
 
250
    # TODO: What if we're in a subdirectory of a branch?  Would like
 
251
    # to allow that, but then the parent may need to understand that
 
252
    # the children have disappeared, or should they be versioned in
 
253
    # both?
 
254
 
 
255
    # TODO: Take an argument/option for branch name.
 
256
    Branch('.', init=True)
 
257
 
 
258
 
 
259
def cmd_diff(revision=None):
 
260
    """Show diff from basis to working copy.
 
261
 
 
262
    :todo: Take one or two revision arguments, look up those trees,
 
263
           and diff them.
 
264
 
 
265
    :todo: Allow diff across branches.
 
266
 
 
267
    :todo: Mangle filenames in diff to be more relevant.
 
268
 
 
269
    :todo: Shouldn't be in the cmd function.
 
270
    """
 
271
 
 
272
    b = Branch('.')
 
273
 
 
274
    if revision == None:
 
275
        old_tree = b.basis_tree()
 
276
    else:
 
277
        old_tree = b.revision_tree(b.lookup_revision(revision))
 
278
        
 
279
    new_tree = b.working_tree()
 
280
    old_inv = old_tree.inventory
 
281
    new_inv = new_tree.inventory
 
282
 
 
283
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
284
    old_label = ''
 
285
    new_label = ''
 
286
 
 
287
    DEVNULL = '/dev/null'
 
288
    # Windows users, don't panic about this filename -- it is a
 
289
    # special signal to GNU patch that the file should be created or
 
290
    # deleted respectively.
 
291
 
 
292
    # TODO: Generation of pseudo-diffs for added/deleted files could
 
293
    # be usefully made into a much faster special case.
 
294
 
 
295
    # TODO: Better to return them in sorted order I think.
 
296
    
 
297
    for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
 
298
        d = None
 
299
 
 
300
        # Don't show this by default; maybe do it if an option is passed
 
301
        # idlabel = '      {%s}' % fid
 
302
        idlabel = ''
 
303
 
 
304
        # FIXME: Something about the diff format makes patch unhappy
 
305
        # with newly-added files.
 
306
 
 
307
        def diffit(*a, **kw):
 
308
            sys.stdout.writelines(difflib.unified_diff(*a, **kw))
 
309
            print
 
310
        
 
311
        if file_state in ['.', '?', 'I']:
 
312
            continue
 
313
        elif file_state == 'A':
 
314
            print '*** added %s %r' % (kind, new_name)
 
315
            if kind == 'file':
 
316
                diffit([],
 
317
                       new_tree.get_file(fid).readlines(),
 
318
                       fromfile=DEVNULL,
 
319
                       tofile=new_label + new_name + idlabel)
 
320
        elif file_state == 'D':
 
321
            assert isinstance(old_name, types.StringTypes)
 
322
            print '*** deleted %s %r' % (kind, old_name)
 
323
            if kind == 'file':
 
324
                diffit(old_tree.get_file(fid).readlines(), [],
 
325
                       fromfile=old_label + old_name + idlabel,
 
326
                       tofile=DEVNULL)
 
327
        elif file_state in ['M', 'R']:
 
328
            if file_state == 'M':
 
329
                assert kind == 'file'
 
330
                assert old_name == new_name
 
331
                print '*** modified %s %r' % (kind, new_name)
 
332
            elif file_state == 'R':
 
333
                print '*** renamed %s %r => %r' % (kind, old_name, new_name)
 
334
 
 
335
            if kind == 'file':
 
336
                diffit(old_tree.get_file(fid).readlines(),
 
337
                       new_tree.get_file(fid).readlines(),
 
338
                       fromfile=old_label + old_name + idlabel,
 
339
                       tofile=new_label + new_name)
 
340
        else:
 
341
            bailout("can't represent state %s {%s}" % (file_state, fid))
 
342
 
 
343
 
 
344
 
 
345
def cmd_log(timezone='original'):
 
346
    """Show log of this branch.
 
347
 
 
348
    :todo: Options for utc; to show ids; to limit range; etc.
 
349
    """
 
350
    Branch('.').write_log(show_timezone=timezone)
 
351
 
 
352
 
 
353
def cmd_ls(revision=None, verbose=False):
 
354
    """List files in a tree.
 
355
 
 
356
    :todo: Take a revision or remote path and list that tree instead.
 
357
    """
 
358
    b = Branch('.')
 
359
    if revision == None:
 
360
        tree = b.working_tree()
 
361
    else:
 
362
        tree = b.revision_tree(b.lookup_revision(revision))
 
363
        
 
364
    for fp, fc, kind, fid in tree.list_files():
 
365
        if verbose:
 
366
            if kind == 'directory':
 
367
                kindch = '/'
 
368
            elif kind == 'file':
 
369
                kindch = ''
 
370
            else:
 
371
                kindch = '???'
 
372
                
 
373
            print '%-8s %s%s' % (fc, fp, kindch)
 
374
        else:
 
375
            print fp
 
376
    
 
377
    
 
378
 
 
379
def cmd_unknowns():
 
380
    """List unknown files"""
 
381
    for f in Branch('.').unknowns():
 
382
        print quotefn(f)
 
383
 
 
384
 
 
385
def cmd_lookup_revision(revno):
 
386
    try:
 
387
        revno = int(revno)
 
388
    except ValueError:
 
389
        bailout("usage: lookup-revision REVNO",
 
390
                ["REVNO is a non-negative revision number for this branch"])
 
391
 
 
392
    print Branch('.').lookup_revision(revno) or NONE_STRING
 
393
 
 
394
 
 
395
 
 
396
def cmd_export(revno, dest):
 
397
    """Export past revision to destination directory."""
 
398
    b = Branch('.')
 
399
    rh = b.lookup_revision(int(revno))
 
400
    t = b.revision_tree(rh)
 
401
    t.export(dest)
 
402
 
 
403
 
 
404
 
 
405
######################################################################
 
406
# internal/test commands
 
407
 
 
408
 
 
409
def cmd_uuid():
 
410
    """Print a newly-generated UUID."""
 
411
    print uuid()
 
412
 
 
413
 
 
414
 
 
415
def cmd_local_time_offset():
 
416
    print bzrlib.osutils.local_time_offset()
 
417
 
 
418
 
 
419
 
 
420
def cmd_commit(message, verbose=False):
 
421
    Branch('.').commit(message, verbose=verbose)
 
422
 
 
423
 
 
424
def cmd_check():
 
425
    """Check consistency of the branch."""
 
426
    check()
 
427
 
 
428
 
 
429
def cmd_is(pred, *rest):
 
430
    """Test whether PREDICATE is true."""
 
431
    try:
 
432
        cmd_handler = globals()['assert_' + pred.replace('-', '_')]
 
433
    except KeyError:
 
434
        bailout("unknown predicate: %s" % quotefn(pred))
 
435
        
 
436
    try:
 
437
        cmd_handler(*rest)
 
438
    except BzrCheckError:
 
439
        # by default we don't print the message so that this can
 
440
        # be used from shell scripts without producing noise
 
441
        sys.exit(1)
 
442
 
 
443
 
 
444
def cmd_username():
 
445
    print bzrlib.osutils.username()
 
446
 
 
447
 
 
448
def cmd_user_email():
 
449
    print bzrlib.osutils.user_email()
 
450
 
 
451
 
 
452
def cmd_gen_revision_id():
 
453
    import time
 
454
    print bzrlib.branch._gen_revision_id(time.time())
 
455
 
 
456
 
 
457
def cmd_doctest():
 
458
    """Run internal doctest suite"""
 
459
    ## -v, if present, is seen by doctest; the argument is just here
 
460
    ## so our parser doesn't complain
 
461
 
 
462
    ## TODO: --verbose option
 
463
    
 
464
    import bzr, doctest, bzrlib.store
 
465
    bzrlib.trace.verbose = False
 
466
    doctest.testmod(bzr)
 
467
    doctest.testmod(bzrlib.store)
 
468
    doctest.testmod(bzrlib.inventory)
 
469
    doctest.testmod(bzrlib.branch)
 
470
    doctest.testmod(bzrlib.osutils)
 
471
    doctest.testmod(bzrlib.tree)
 
472
 
 
473
    # more strenuous tests;
 
474
    import bzrlib.tests
 
475
    doctest.testmod(bzrlib.tests)
 
476
 
 
477
 
 
478
######################################################################
 
479
# help
 
480
 
 
481
 
 
482
def cmd_help():
 
483
    # TODO: Specific help for particular commands
 
484
    print __doc__
 
485
 
 
486
 
 
487
def cmd_version():
 
488
    print "bzr (bazaar-ng) %s" % __version__
 
489
    print __copyright__
 
490
    print "http://bazaar-ng.org/"
 
491
    print
 
492
    print \
 
493
"""bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and
 
494
you may use, modify and redistribute it under the terms of the GNU 
 
495
General Public License version 2 or later."""
 
496
 
 
497
 
 
498
def cmd_rocks():
 
499
    """Statement of optimism."""
 
500
    print "it sure does!"
 
501
 
 
502
 
 
503
 
 
504
######################################################################
 
505
# main routine
 
506
 
 
507
 
 
508
# list of all available options; the rhs can be either None for an
 
509
# option that takes no argument, or a constructor function that checks
 
510
# the type.
 
511
OPTIONS = {
 
512
    'all':                    None,
 
513
    'help':                   None,
 
514
    'message':                unicode,
 
515
    'revision':               int,
 
516
    'show-ids':               None,
 
517
    'timezone':               str,
 
518
    'verbose':                None,
 
519
    'version':                None,
 
520
    }
 
521
 
 
522
SHORT_OPTIONS = {
 
523
    'm':                      'message',
 
524
    'r':                      'revision',
 
525
    'v':                      'verbose',
 
526
}
 
527
 
 
528
# List of options that apply to particular commands; commands not
 
529
# listed take none.
 
530
cmd_options = {
 
531
    'add':                    ['verbose'],
 
532
    'commit':                 ['message', 'verbose'],
 
533
    'diff':                   ['revision'],
 
534
    'inventory':              ['revision'],
 
535
    'log':                    ['show-ids', 'timezone'],
 
536
    'ls':                     ['revision', 'verbose'],
 
537
    'remove':                 ['verbose'],
 
538
    'status':                 ['all'],
 
539
    }
 
540
 
 
541
 
 
542
cmd_args = {
 
543
    'init':                   [],
 
544
    'add':                    ['file+'],
 
545
    'commit':                 [],
 
546
    'diff':                   [],
 
547
    'file-id':                ['filename'],
 
548
    'get-file-text':          ['text_id'],
 
549
    'get-inventory':          ['inventory_id'],
 
550
    'get-revision':           ['revision_id'],
 
551
    'get-revision-inventory': ['revision_id'],
 
552
    'log':                    [],
 
553
    'lookup-revision':        ['revno'],
 
554
    'export':                 ['revno', 'dest'],
 
555
    'remove':                 ['file+'],
 
556
    'status':                 [],
 
557
    }
 
558
 
 
559
 
 
560
def parse_args(argv):
 
561
    """Parse command line.
 
562
    
 
563
    Arguments and options are parsed at this level before being passed
 
564
    down to specific command handlers.  This routine knows, from a
 
565
    lookup table, something about the available options, what optargs
 
566
    they take, and which commands will accept them.
 
567
 
 
568
    >>> parse_args('bzr --help'.split())
 
569
    ([], {'help': True})
 
570
    >>> parse_args('bzr --version'.split())
 
571
    ([], {'version': True})
 
572
    >>> parse_args('bzr status --all'.split())
 
573
    (['status'], {'all': True})
 
574
    >>> parse_args('bzr commit --message=biter'.split())
 
575
    (['commit'], {'message': u'biter'})
 
576
    """
 
577
    args = []
 
578
    opts = {}
 
579
 
 
580
    # TODO: Maybe handle '--' to end options?
 
581
 
 
582
    it = iter(argv[1:])
 
583
    while it:
 
584
        a = it.next()
 
585
        if a[0] == '-':
 
586
            optarg = None
 
587
            if a[1] == '-':
 
588
                mutter("  got option %r" % a)
 
589
                if '=' in a:
 
590
                    optname, optarg = a[2:].split('=', 1)
 
591
                else:
 
592
                    optname = a[2:]
 
593
                if optname not in OPTIONS:
 
594
                    bailout('unknown long option %r' % a)
 
595
            else:
 
596
                shortopt = a[1:]
 
597
                if shortopt not in SHORT_OPTIONS:
 
598
                    bailout('unknown short option %r' % a)
 
599
                optname = SHORT_OPTIONS[shortopt]
 
600
            
 
601
            if optname in opts:
 
602
                # XXX: Do we ever want to support this, e.g. for -r?
 
603
                bailout('repeated option %r' % a)
 
604
                
 
605
            optargfn = OPTIONS[optname]
 
606
            if optargfn:
 
607
                if optarg == None:
 
608
                    if not it:
 
609
                        bailout('option %r needs an argument' % a)
 
610
                    else:
 
611
                        optarg = it.next()
 
612
                opts[optname] = optargfn(optarg)
 
613
                mutter("    option argument %r" % opts[optname])
 
614
            else:
 
615
                if optarg != None:
 
616
                    bailout('option %r takes no argument' % optname)
 
617
                opts[optname] = True
 
618
        else:
 
619
            args.append(a)
 
620
 
 
621
    return args, opts
 
622
 
 
623
 
 
624
 
 
625
def _match_args(cmd, args):
 
626
    """Check non-option arguments match required pattern.
 
627
 
 
628
    >>> _match_args('status', ['asdasdsadasd'])
 
629
    Traceback (most recent call last):
 
630
    ...
 
631
    BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
 
632
    >>> _match_args('add', ['asdasdsadasd'])
 
633
    {'file_list': ['asdasdsadasd']}
 
634
    >>> _match_args('add', 'abc def gj'.split())
 
635
    {'file_list': ['abc', 'def', 'gj']}
 
636
    """
 
637
    # match argument pattern
 
638
    argform = cmd_args.get(cmd, [])
 
639
    argdict = {}
 
640
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
 
641
    # all but one.
 
642
    for ap in argform:
 
643
        argname = ap[:-1]
 
644
        if ap[-1] == '?':
 
645
            assert 0
 
646
        elif ap[-1] == '*':
 
647
            assert 0
 
648
        elif ap[-1] == '+':
 
649
            if not args:
 
650
                bailout("command %r needs one or more %s"
 
651
                        % (cmd, argname.upper()))
 
652
            else:
 
653
                argdict[argname + '_list'] = args[:]
 
654
                args = []
 
655
        else:
 
656
            # just a plain arg
 
657
            argname = ap
 
658
            if not args:
 
659
                bailout("command %r requires argument %s"
 
660
                        % (cmd, argname.upper()))
 
661
            else:
 
662
                argdict[argname] = args.pop(0)
 
663
            
 
664
    if args:
 
665
        bailout("extra arguments to command %s: %r"
 
666
                % (cmd, args))
 
667
 
 
668
    return argdict
 
669
 
 
670
 
 
671
 
 
672
def run_bzr(argv):
 
673
    """Execute a command.
 
674
 
 
675
    This is similar to main(), but without all the trappings for
 
676
    logging and error handling.
 
677
    """
 
678
    try:
 
679
        args, opts = parse_args(argv)
 
680
        if 'help' in opts:
 
681
            # TODO: pass down other arguments in case they asked for
 
682
            # help on a command name?
 
683
            cmd_help()
 
684
            return 0
 
685
        elif 'version' in opts:
 
686
            cmd_version()
 
687
            return 0
 
688
        cmd = args.pop(0)
 
689
    except IndexError:
 
690
        log_error('usage: bzr COMMAND\n')
 
691
        log_error('  try "bzr help"\n')
 
692
        return 1
 
693
            
 
694
    try:
 
695
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
 
696
    except KeyError:
 
697
        bailout("unknown command " + `cmd`)
 
698
 
 
699
    # TODO: special --profile option to turn on the Python profiler
 
700
 
 
701
    # check options are reasonable
 
702
    allowed = cmd_options.get(cmd, [])
 
703
    for oname in opts:
 
704
        if oname not in allowed:
 
705
            bailout("option %r is not allowed for command %r"
 
706
                    % (oname, cmd))
 
707
 
 
708
    cmdargs = _match_args(cmd, args)
 
709
    cmdargs.update(opts)
 
710
 
 
711
    ret = cmd_handler(**cmdargs) or 0
 
712
 
 
713
 
 
714
 
 
715
def main(argv):
 
716
    ## TODO: Handle command-line options; probably know what options are valid for
 
717
    ## each command
 
718
 
 
719
    ## TODO: If the arguments are wrong, give a usage message rather
 
720
    ## than just a backtrace.
 
721
 
 
722
    try:
 
723
        t = bzrlib.trace._tracefile
 
724
        t.write('-' * 60 + '\n')
 
725
        t.write('bzr invoked at %s\n' % format_date(time.time()))
 
726
        t.write('  by %s on %s\n' % (bzrlib.osutils.username(), socket.gethostname()))
 
727
        t.write('  arguments: %r\n' % argv)
 
728
 
 
729
        starttime = os.times()[4]
 
730
 
 
731
        import platform
 
732
        t.write('  platform: %s\n' % platform.platform())
 
733
        t.write('  python: %s\n' % platform.python_version())
 
734
 
 
735
        ret = run_bzr(argv)
 
736
        
 
737
        times = os.times()
 
738
        mutter("finished, %.3fu/%.3fs cpu, %.3fu/%.3fs cum"
 
739
               % times[:4])
 
740
        mutter("    %.3f elapsed" % (times[4] - starttime))
 
741
 
 
742
        return ret
 
743
    except BzrError, e:
 
744
        log_error('bzr: error: ' + e.args[0] + '\n')
 
745
        if len(e.args) > 1:
 
746
            for h in e.args[1]:
 
747
                log_error('  ' + h + '\n')
 
748
        return 1
 
749
    except Exception, e:
 
750
        log_error('bzr: exception: %s\n' % e)
 
751
        log_error('    see .bzr.log for details\n')
 
752
        traceback.print_exc(None, bzrlib.trace._tracefile)
 
753
        traceback.print_exc(None, sys.stderr)
 
754
        return 1
 
755
 
 
756
    # TODO: Maybe nicer handling of IOError?
 
757
 
 
758
 
 
759
 
 
760
if __name__ == '__main__':
 
761
    sys.exit(main(sys.argv))
 
762
    ##import profile
 
763
    ##profile.run('main(sys.argv)')
 
764