/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-22 07:28:45 UTC
  • Revision ID: mbp@sourcefrog.net-20050322072845-5859efd4dab29169
use abspath() for the function that makes an absolute
path to something in a branch or tree

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