/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

  • Committer: mbp at sourcefrog
  • Date: 2005-03-25 01:16:46 UTC
  • Revision ID: mbp@sourcefrog.net-20050325011646-e3f0af5d6bd1190c
- update version string
- put it in bzrlib

Show diffs side-by-side

added added

removed removed

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