/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 bzrlib/commands.py

Fix formatting, remove catch-all for exceptions when opening local repositories.

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_file_text(text_id):
145
 
    """Get contents of a file by hash."""
146
 
    sf = Branch('.').text_store[text_id]
147
 
    pumpfile(sf, sys.stdout)
148
 
 
149
 
 
150
 
 
151
 
######################################################################
152
 
# commands
153
 
    
154
 
 
155
 
def cmd_revno():
156
 
    """Show number of revisions on this branch"""
157
 
    print Branch('.').revno()
158
 
    
159
 
 
160
 
def cmd_add(file_list, verbose=False):
161
 
    """Add specified files.
162
 
    
163
 
    Fails if the files are already added.
164
 
    """
165
 
    Branch('.').add(file_list, verbose=verbose)
166
 
 
167
 
 
168
 
def cmd_inventory(revision=None):
169
 
    """Show inventory of the current working copy."""
170
 
    ## TODO: Also optionally show a previous inventory
171
 
    ## TODO: Format options
172
 
    b = Branch('.')
173
 
    if revision == None:
174
 
        inv = b.read_working_inventory()
175
 
    else:
176
 
        inv = b.get_revision_inventory(b.lookup_revision(revision))
177
 
        
178
 
    for path, entry in inv.iter_entries():
179
 
        print '%-50s %s' % (entry.file_id, path)
180
 
 
181
 
 
182
 
 
183
 
def cmd_info():
184
 
    b = Branch('.')
185
 
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
186
 
 
187
 
    def plural(n, base='', pl=None):
188
 
        if n == 1:
189
 
            return base
190
 
        elif pl is not None:
191
 
            return pl
192
 
        else:
193
 
            return 's'
194
 
 
195
 
    count_version_dirs = 0
196
 
 
197
 
    count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
198
 
    for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
199
 
        fs = st_tup[0]
200
 
        count_status[fs] += 1
201
 
        if fs not in ['I', '?'] and st_tup[4] == 'directory':
202
 
            count_version_dirs += 1
203
 
 
204
 
    print
205
 
    print 'in the working tree:'
206
 
    for name, fs in (('unchanged', '.'),
207
 
                     ('modified', 'M'), ('added', 'A'), ('removed', 'D'),
208
 
                     ('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
209
 
                     ):
210
 
        print '  %5d %s' % (count_status[fs], name)
211
 
    print '  %5d versioned subdirector%s' % (count_version_dirs,
212
 
                                             plural(count_version_dirs, 'y', 'ies'))
213
 
 
214
 
    print
215
 
    print 'branch history:'
216
 
    history = b.revision_history()
217
 
    revno = len(history)
218
 
    print '  %5d revision%s' % (revno, plural(revno))
219
 
    committers = Set()
220
 
    for rev in history:
221
 
        committers.add(b.get_revision(rev).committer)
222
 
    print '  %5d committer%s' % (len(committers), plural(len(committers)))
223
 
    if revno > 0:
224
 
        firstrev = b.get_revision(history[0])
225
 
        age = int((time.time() - firstrev.timestamp) / 3600 / 24)
226
 
        print '  %5d day%s old' % (age, plural(age))
227
 
        print '  first revision: %s' % format_date(firstrev.timestamp,
228
 
                                                 firstrev.timezone)
229
 
 
230
 
        lastrev = b.get_revision(history[-1])
231
 
        print '  latest revision: %s' % format_date(lastrev.timestamp,
232
 
                                                    lastrev.timezone)
233
 
        
234
 
    
235
 
 
236
 
 
237
 
def cmd_remove(file_list, verbose=False):
238
 
    Branch('.').remove(file_list, verbose=verbose)
239
 
 
240
 
 
241
 
 
242
 
def cmd_file_id(filename):
243
 
    i = Branch('.').read_working_inventory().path2id(filename)
244
 
    if i is None:
245
 
        bailout("%s is not a versioned file" % filename)
246
 
    else:
247
 
        print i
248
 
 
249
 
 
250
 
def cmd_find_filename(fileid):
251
 
    n = find_filename(fileid)
252
 
    if n is None:
253
 
        bailout("%s is not a live file id" % fileid)
254
 
    else:
255
 
        print n
256
 
 
257
 
 
258
 
def cmd_revision_history():
259
 
    for patchid in Branch('.').revision_history():
260
 
        print patchid
261
 
 
262
 
 
263
 
 
264
 
def cmd_init():
265
 
    # TODO: Check we're not already in a working directory?  At the
266
 
    # moment you'll get an ugly error.
267
 
    
268
 
    # TODO: What if we're in a subdirectory of a branch?  Would like
269
 
    # to allow that, but then the parent may need to understand that
270
 
    # the children have disappeared, or should they be versioned in
271
 
    # both?
272
 
 
273
 
    # TODO: Take an argument/option for branch name.
274
 
    Branch('.', init=True)
275
 
 
276
 
 
277
 
def cmd_diff(revision=None):
278
 
    """Show diff from basis to working copy.
279
 
 
280
 
    :todo: Take one or two revision arguments, look up those trees,
281
 
           and diff them.
282
 
 
283
 
    :todo: Allow diff across branches.
284
 
 
285
 
    :todo: Mangle filenames in diff to be more relevant.
286
 
 
287
 
    :todo: Shouldn't be in the cmd function.
288
 
    """
289
 
 
290
 
    b = Branch('.')
291
 
 
292
 
    if revision == None:
293
 
        old_tree = b.basis_tree()
294
 
    else:
295
 
        old_tree = b.revision_tree(b.lookup_revision(revision))
296
 
        
297
 
    new_tree = b.working_tree()
298
 
    old_inv = old_tree.inventory
299
 
    new_inv = new_tree.inventory
300
 
 
301
 
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
302
 
    old_label = ''
303
 
    new_label = ''
304
 
 
305
 
    DEVNULL = '/dev/null'
306
 
    # Windows users, don't panic about this filename -- it is a
307
 
    # special signal to GNU patch that the file should be created or
308
 
    # deleted respectively.
309
 
 
310
 
    # TODO: Generation of pseudo-diffs for added/deleted files could
311
 
    # be usefully made into a much faster special case.
312
 
 
313
 
    # TODO: Better to return them in sorted order I think.
314
 
    
315
 
    for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
316
 
        d = None
317
 
 
318
 
        # Don't show this by default; maybe do it if an option is passed
319
 
        # idlabel = '      {%s}' % fid
320
 
        idlabel = ''
321
 
 
322
 
        # FIXME: Something about the diff format makes patch unhappy
323
 
        # with newly-added files.
324
 
 
325
 
        def diffit(*a, **kw):
326
 
            sys.stdout.writelines(difflib.unified_diff(*a, **kw))
327
 
            print
328
 
        
329
 
        if file_state in ['.', '?', 'I']:
330
 
            continue
331
 
        elif file_state == 'A':
332
 
            print '*** added %s %r' % (kind, new_name)
333
 
            if kind == 'file':
334
 
                diffit([],
335
 
                       new_tree.get_file(fid).readlines(),
336
 
                       fromfile=DEVNULL,
337
 
                       tofile=new_label + new_name + idlabel)
338
 
        elif file_state == 'D':
339
 
            assert isinstance(old_name, types.StringTypes)
340
 
            print '*** deleted %s %r' % (kind, old_name)
341
 
            if kind == 'file':
342
 
                diffit(old_tree.get_file(fid).readlines(), [],
343
 
                       fromfile=old_label + old_name + idlabel,
344
 
                       tofile=DEVNULL)
345
 
        elif file_state in ['M', 'R']:
346
 
            if file_state == 'M':
347
 
                assert kind == 'file'
348
 
                assert old_name == new_name
349
 
                print '*** modified %s %r' % (kind, new_name)
350
 
            elif file_state == 'R':
351
 
                print '*** renamed %s %r => %r' % (kind, old_name, new_name)
352
 
 
353
 
            if kind == 'file':
354
 
                diffit(old_tree.get_file(fid).readlines(),
355
 
                       new_tree.get_file(fid).readlines(),
356
 
                       fromfile=old_label + old_name + idlabel,
357
 
                       tofile=new_label + new_name)
358
 
        else:
359
 
            bailout("can't represent state %s {%s}" % (file_state, fid))
360
 
 
361
 
 
362
 
 
363
 
def cmd_log(timezone='original'):
364
 
    """Show log of this branch.
365
 
 
366
 
    :todo: Options for utc; to show ids; to limit range; etc.
367
 
    """
368
 
    Branch('.').write_log(show_timezone=timezone)
369
 
 
370
 
 
371
 
def cmd_ls(revision=None, verbose=False):
372
 
    """List files in a tree.
373
 
 
374
 
    :todo: Take a revision or remote path and list that tree instead.
375
 
    """
376
 
    b = Branch('.')
377
 
    if revision == None:
378
 
        tree = b.working_tree()
379
 
    else:
380
 
        tree = b.revision_tree(b.lookup_revision(revision))
381
 
        
382
 
    for fp, fc, kind, fid in tree.list_files():
383
 
        if verbose:
384
 
            if kind == 'directory':
385
 
                kindch = '/'
386
 
            elif kind == 'file':
387
 
                kindch = ''
388
 
            else:
389
 
                kindch = '???'
390
 
                
391
 
            print '%-8s %s%s' % (fc, fp, kindch)
392
 
        else:
393
 
            print fp
394
 
    
395
 
    
396
 
 
397
 
def cmd_unknowns():
398
 
    """List unknown files"""
399
 
    for f in Branch('.').unknowns():
400
 
        print quotefn(f)
401
 
 
402
 
 
403
 
def cmd_lookup_revision(revno):
404
 
    try:
405
 
        revno = int(revno)
406
 
    except ValueError:
407
 
        bailout("usage: lookup-revision REVNO",
408
 
                ["REVNO is a non-negative revision number for this branch"])
409
 
 
410
 
    print Branch('.').lookup_revision(revno) or NONE_STRING
411
 
 
412
 
 
413
 
 
414
 
def cmd_export(revno, dest):
415
 
    """Export past revision to destination directory."""
416
 
    b = Branch('.')
417
 
    rh = b.lookup_revision(int(revno))
418
 
    t = b.revision_tree(rh)
419
 
    t.export(dest)
420
 
 
421
 
 
422
 
 
423
 
######################################################################
424
 
# internal/test commands
425
 
 
426
 
 
427
 
def cmd_uuid():
428
 
    """Print a newly-generated UUID."""
429
 
    print uuid()
430
 
 
431
 
 
432
 
 
433
 
def cmd_local_time_offset():
434
 
    print bzrlib.osutils.local_time_offset()
435
 
 
436
 
 
437
 
 
438
 
def cmd_commit(message, verbose=False):
439
 
    Branch('.').commit(message, verbose=verbose)
440
 
 
441
 
 
442
 
def cmd_check():
443
 
    """Check consistency of the branch."""
444
 
    check()
445
 
 
446
 
 
447
 
def cmd_is(pred, *rest):
448
 
    """Test whether PREDICATE is true."""
449
 
    try:
450
 
        cmd_handler = globals()['assert_' + pred.replace('-', '_')]
451
 
    except KeyError:
452
 
        bailout("unknown predicate: %s" % quotefn(pred))
453
 
        
454
 
    try:
455
 
        cmd_handler(*rest)
456
 
    except BzrCheckError:
457
 
        # by default we don't print the message so that this can
458
 
        # be used from shell scripts without producing noise
459
 
        sys.exit(1)
460
 
 
461
 
 
462
 
def cmd_username():
463
 
    print bzrlib.osutils.username()
464
 
 
465
 
 
466
 
def cmd_user_email():
467
 
    print bzrlib.osutils.user_email()
468
 
 
469
 
 
470
 
def cmd_gen_revision_id():
471
 
    import time
472
 
    print bzrlib.branch._gen_revision_id(time.time())
473
 
 
474
 
 
475
 
def cmd_doctest():
476
 
    """Run internal doctest suite"""
477
 
    ## -v, if present, is seen by doctest; the argument is just here
478
 
    ## so our parser doesn't complain
479
 
 
480
 
    ## TODO: --verbose option
481
 
    
482
 
    import bzr, doctest, bzrlib.store
483
 
    bzrlib.trace.verbose = False
484
 
    doctest.testmod(bzr)
485
 
    doctest.testmod(bzrlib.store)
486
 
    doctest.testmod(bzrlib.inventory)
487
 
    doctest.testmod(bzrlib.branch)
488
 
    doctest.testmod(bzrlib.osutils)
489
 
    doctest.testmod(bzrlib.tree)
490
 
 
491
 
    # more strenuous tests;
492
 
    import bzrlib.tests
493
 
    doctest.testmod(bzrlib.tests)
494
 
 
495
 
 
496
 
######################################################################
497
 
# help
498
 
 
499
 
 
500
 
def cmd_help():
501
 
    # TODO: Specific help for particular commands
502
 
    print __doc__
503
 
 
504
 
 
505
 
def cmd_version():
506
 
    print "bzr (bazaar-ng) %s" % __version__
507
 
    print __copyright__
508
 
    print "http://bazaar-ng.org/"
509
 
    print
510
 
    print \
511
 
"""bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and
512
 
you may use, modify and redistribute it under the terms of the GNU 
513
 
General Public License version 2 or later."""
514
 
 
515
 
 
516
 
def cmd_rocks():
517
 
    """Statement of optimism."""
518
 
    print "it sure does!"
519
 
 
520
 
 
521
 
 
522
 
######################################################################
523
 
# main routine
524
 
 
525
 
 
526
 
# list of all available options; the rhs can be either None for an
527
 
# option that takes no argument, or a constructor function that checks
528
 
# the type.
529
 
OPTIONS = {
530
 
    'all':                    None,
531
 
    'help':                   None,
532
 
    'message':                unicode,
533
 
    'revision':               int,
534
 
    'show-ids':               None,
535
 
    'timezone':               str,
536
 
    'verbose':                None,
537
 
    'version':                None,
538
 
    }
539
 
 
540
 
SHORT_OPTIONS = {
541
 
    'm':                      'message',
542
 
    'r':                      'revision',
543
 
    'v':                      'verbose',
544
 
}
545
 
 
546
 
# List of options that apply to particular commands; commands not
547
 
# listed take none.
548
 
cmd_options = {
549
 
    'add':                    ['verbose'],
550
 
    'commit':                 ['message', 'verbose'],
551
 
    'diff':                   ['revision'],
552
 
    'inventory':              ['revision'],
553
 
    'log':                    ['show-ids', 'timezone'],
554
 
    'ls':                     ['revision', 'verbose'],
555
 
    'remove':                 ['verbose'],
556
 
    'status':                 ['all'],
557
 
    }
558
 
 
559
 
 
560
 
cmd_args = {
561
 
    'init':                   [],
562
 
    'add':                    ['file+'],
563
 
    'commit':                 [],
564
 
    'diff':                   [],
565
 
    'file-id':                ['filename'],
566
 
    'get-file-text':          ['text_id'],
567
 
    'get-inventory':          ['inventory_id'],
568
 
    'get-revision':           ['revision_id'],
569
 
    'get-revision-inventory': ['revision_id'],
570
 
    'log':                    [],
571
 
    'lookup-revision':        ['revno'],
572
 
    'export':                 ['revno', 'dest'],
573
 
    'remove':                 ['file+'],
574
 
    'status':                 [],
575
 
    }
576
 
 
577
 
 
578
 
def parse_args(argv):
579
 
    """Parse command line.
580
 
    
581
 
    Arguments and options are parsed at this level before being passed
582
 
    down to specific command handlers.  This routine knows, from a
583
 
    lookup table, something about the available options, what optargs
584
 
    they take, and which commands will accept them.
585
 
 
586
 
    >>> parse_args('--help'.split())
587
 
    ([], {'help': True})
588
 
    >>> parse_args('--version'.split())
589
 
    ([], {'version': True})
590
 
    >>> parse_args('status --all'.split())
591
 
    (['status'], {'all': True})
592
 
    >>> parse_args('commit --message=biter'.split())
593
 
    (['commit'], {'message': u'biter'})
594
 
    """
595
 
    args = []
596
 
    opts = {}
597
 
 
598
 
    # TODO: Maybe handle '--' to end options?
599
 
 
600
 
    while argv:
601
 
        a = argv.pop(0)
602
 
        if a[0] == '-':
603
 
            optarg = None
604
 
            if a[1] == '-':
605
 
                mutter("  got option %r" % a)
606
 
                if '=' in a:
607
 
                    optname, optarg = a[2:].split('=', 1)
608
 
                else:
609
 
                    optname = a[2:]
610
 
                if optname not in OPTIONS:
611
 
                    bailout('unknown long option %r' % a)
612
 
            else:
613
 
                shortopt = a[1:]
614
 
                if shortopt not in SHORT_OPTIONS:
615
 
                    bailout('unknown short option %r' % a)
616
 
                optname = SHORT_OPTIONS[shortopt]
617
 
            
618
 
            if optname in opts:
619
 
                # XXX: Do we ever want to support this, e.g. for -r?
620
 
                bailout('repeated option %r' % a)
621
 
                
622
 
            optargfn = OPTIONS[optname]
623
 
            if optargfn:
624
 
                if optarg == None:
625
 
                    if not argv:
626
 
                        bailout('option %r needs an argument' % a)
627
 
                    else:
628
 
                        optarg = argv.pop(0)
629
 
                opts[optname] = optargfn(optarg)
630
 
                mutter("    option argument %r" % opts[optname])
631
 
            else:
632
 
                if optarg != None:
633
 
                    bailout('option %r takes no argument' % optname)
634
 
                opts[optname] = True
635
 
        else:
636
 
            args.append(a)
637
 
 
638
 
    return args, opts
639
 
 
640
 
 
641
 
 
642
 
def _match_args(cmd, args):
643
 
    """Check non-option arguments match required pattern.
644
 
 
645
 
    >>> _match_args('status', ['asdasdsadasd'])
646
 
    Traceback (most recent call last):
647
 
    ...
648
 
    BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
649
 
    >>> _match_args('add', ['asdasdsadasd'])
650
 
    {'file_list': ['asdasdsadasd']}
651
 
    >>> _match_args('add', 'abc def gj'.split())
652
 
    {'file_list': ['abc', 'def', 'gj']}
653
 
    """
654
 
    # match argument pattern
655
 
    argform = cmd_args.get(cmd, [])
656
 
    argdict = {}
657
 
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
658
 
    # all but one.
659
 
 
660
 
    for ap in argform:
661
 
        argname = ap[:-1]
662
 
        if ap[-1] == '?':
663
 
            assert 0
664
 
        elif ap[-1] == '*':
665
 
            assert 0
666
 
        elif ap[-1] == '+':
667
 
            if not args:
668
 
                bailout("command %r needs one or more %s"
669
 
                        % (cmd, argname.upper()))
670
 
            else:
671
 
                argdict[argname + '_list'] = args[:]
672
 
                args = []
673
 
        else:
674
 
            # just a plain arg
675
 
            argname = ap
676
 
            if not args:
677
 
                bailout("command %r requires argument %s"
678
 
                        % (cmd, argname.upper()))
679
 
            else:
680
 
                argdict[argname] = args.pop(0)
681
 
            
682
 
    if args:
683
 
        bailout("extra arguments to command %s: %r"
684
 
                % (cmd, args))
685
 
 
686
 
    return argdict
687
 
 
688
 
 
689
 
 
690
 
def run_bzr(argv):
691
 
    """Execute a command.
692
 
 
693
 
    This is similar to main(), but without all the trappings for
694
 
    logging and error handling.
695
 
    """
696
 
    try:
697
 
        args, opts = parse_args(argv[1:])
698
 
        if 'help' in opts:
699
 
            # TODO: pass down other arguments in case they asked for
700
 
            # help on a command name?
701
 
            cmd_help()
702
 
            return 0
703
 
        elif 'version' in opts:
704
 
            cmd_version()
705
 
            return 0
706
 
        cmd = args.pop(0)
707
 
    except IndexError:
708
 
        log_error('usage: bzr COMMAND\n')
709
 
        log_error('  try "bzr help"\n')
710
 
        return 1
711
 
            
712
 
    try:
713
 
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
714
 
    except KeyError:
715
 
        bailout("unknown command " + `cmd`)
716
 
 
717
 
    # TODO: special --profile option to turn on the Python profiler
718
 
 
719
 
    # check options are reasonable
720
 
    allowed = cmd_options.get(cmd, [])
721
 
    for oname in opts:
722
 
        if oname not in allowed:
723
 
            bailout("option %r is not allowed for command %r"
724
 
                    % (oname, cmd))
725
 
 
726
 
    cmdargs = _match_args(cmd, args)
727
 
    cmdargs.update(opts)
728
 
 
729
 
    ret = cmd_handler(**cmdargs) or 0
730
 
 
731
 
 
732
 
 
733
 
def main(argv):
734
 
    ## TODO: Handle command-line options; probably know what options are valid for
735
 
    ## each command
736
 
 
737
 
    ## TODO: If the arguments are wrong, give a usage message rather
738
 
    ## than just a backtrace.
739
 
 
740
 
    try:
741
 
        t = bzrlib.trace._tracefile
742
 
        t.write('-' * 60 + '\n')
743
 
        t.write('bzr invoked at %s\n' % format_date(time.time()))
744
 
        t.write('  by %s on %s\n' % (bzrlib.osutils.username(), socket.gethostname()))
745
 
        t.write('  arguments: %r\n' % argv)
746
 
 
747
 
        starttime = os.times()[4]
748
 
 
749
 
        import platform
750
 
        t.write('  platform: %s\n' % platform.platform())
751
 
        t.write('  python: %s\n' % platform.python_version())
752
 
 
753
 
        ret = run_bzr(argv)
754
 
        
755
 
        times = os.times()
756
 
        mutter("finished, %.3fu/%.3fs cpu, %.3fu/%.3fs cum"
757
 
               % times[:4])
758
 
        mutter("    %.3f elapsed" % (times[4] - starttime))
759
 
 
760
 
        return ret
761
 
    except BzrError, e:
762
 
        log_error('bzr: error: ' + e.args[0] + '\n')
763
 
        if len(e.args) > 1:
764
 
            for h in e.args[1]:
765
 
                log_error('  ' + h + '\n')
766
 
        return 1
767
 
    except Exception, e:
768
 
        log_error('bzr: exception: %s\n' % e)
769
 
        log_error('    see .bzr.log for details\n')
770
 
        traceback.print_exc(None, bzrlib.trace._tracefile)
771
 
        traceback.print_exc(None, sys.stderr)
772
 
        return 1
773
 
 
774
 
    # TODO: Maybe nicer handling of IOError?
775
 
 
776
 
 
777
 
 
778
 
if __name__ == '__main__':
779
 
    sys.exit(main(sys.argv))
780
 
    ##import profile
781
 
    ##profile.run('main(sys.argv)')
782