/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
 
# Copyright (C) 2004, 2005 by Canonical Ltd
2
 
 
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
 
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
 
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
 
19
 
import sys, os, time, os.path
20
 
from sets import Set
21
 
 
22
 
import bzrlib
23
 
from bzrlib.trace import mutter, note, log_error
24
 
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
25
 
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
26
 
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
27
 
from bzrlib.revision import Revision
28
 
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
29
 
     format_date
30
 
 
31
 
 
32
 
def _squish_command_name(cmd):
33
 
    return 'cmd_' + cmd.replace('-', '_')
34
 
 
35
 
 
36
 
def _unsquish_command_name(cmd):
37
 
    assert cmd.startswith("cmd_")
38
 
    return cmd[4:].replace('_','-')
39
 
 
40
 
def get_all_cmds():
41
 
    """Return canonical name and class for all registered commands."""
42
 
    for k, v in globals().iteritems():
43
 
        if k.startswith("cmd_"):
44
 
            yield _unsquish_command_name(k), v
45
 
 
46
 
def get_cmd_class(cmd):
47
 
    """Return the canonical name and command class for a command.
48
 
    """
49
 
    cmd = str(cmd)                      # not unicode
50
 
 
51
 
    # first look up this command under the specified name
52
 
    try:
53
 
        return cmd, globals()[_squish_command_name(cmd)]
54
 
    except KeyError:
55
 
        pass
56
 
 
57
 
    # look for any command which claims this as an alias
58
 
    for cmdname, cmdclass in get_all_cmds():
59
 
        if cmd in cmdclass.aliases:
60
 
            return cmdname, cmdclass
61
 
    else:
62
 
        raise BzrCommandError("unknown command %r" % cmd)
63
 
 
64
 
 
65
 
class Command:
66
 
    """Base class for commands.
67
 
 
68
 
    The docstring for an actual command should give a single-line
69
 
    summary, then a complete description of the command.  A grammar
70
 
    description will be inserted.
71
 
 
72
 
    takes_args
73
 
        List of argument forms, marked with whether they are optional,
74
 
        repeated, etc.
75
 
 
76
 
    takes_options
77
 
        List of options that may be given for this command.
78
 
 
79
 
    hidden
80
 
        If true, this command isn't advertised.
81
 
    """
82
 
    aliases = []
83
 
    
84
 
    takes_args = []
85
 
    takes_options = []
86
 
 
87
 
    hidden = False
88
 
    
89
 
    def __init__(self, options, arguments):
90
 
        """Construct and run the command.
91
 
 
92
 
        Sets self.status to the return value of run()."""
93
 
        assert isinstance(options, dict)
94
 
        assert isinstance(arguments, dict)
95
 
        cmdargs = options.copy()
96
 
        cmdargs.update(arguments)
97
 
        assert self.__doc__ != Command.__doc__, \
98
 
               ("No help message set for %r" % self)
99
 
        self.status = self.run(**cmdargs)
100
 
 
101
 
    
102
 
    def run(self):
103
 
        """Override this in sub-classes.
104
 
 
105
 
        This is invoked with the options and arguments bound to
106
 
        keyword parameters.
107
 
 
108
 
        Return 0 or None if the command was successful, or a shell
109
 
        error code if not.
110
 
        """
111
 
        return 0
112
 
 
113
 
 
114
 
 
115
 
class cmd_status(Command):
116
 
    """Display status summary.
117
 
 
118
 
    For each file there is a single line giving its file state and name.
119
 
    The name is that in the current revision unless it is deleted or
120
 
    missing, in which case the old name is shown.
121
 
    """
122
 
    takes_args = ['file*']
123
 
    takes_options = ['all']
124
 
    aliases = ['st', 'stat']
125
 
    
126
 
    def run(self, all=False, file_list=None):
127
 
        #import bzrlib.status
128
 
        #bzrlib.status.tree_status(Branch('.'))
129
 
        Branch('.').show_status(show_all=all, file_list=file_list)
130
 
 
131
 
 
132
 
class cmd_cat_revision(Command):
133
 
    """Write out metadata for a revision."""
134
 
 
135
 
    hidden = True
136
 
    takes_args = ['revision_id']
137
 
    
138
 
    def run(self, revision_id):
139
 
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
140
 
 
141
 
 
142
 
class cmd_revno(Command):
143
 
    """Show current revision number.
144
 
 
145
 
    This is equal to the number of revisions on this branch."""
146
 
    def run(self):
147
 
        print Branch('.').revno()
148
 
 
149
 
    
150
 
class cmd_add(Command):
151
 
    """Add specified files or directories.
152
 
 
153
 
    In non-recursive mode, all the named items are added, regardless
154
 
    of whether they were previously ignored.  A warning is given if
155
 
    any of the named files are already versioned.
156
 
 
157
 
    In recursive mode (the default), files are treated the same way
158
 
    but the behaviour for directories is different.  Directories that
159
 
    are already versioned do not give a warning.  All directories,
160
 
    whether already versioned or not, are searched for files or
161
 
    subdirectories that are neither versioned or ignored, and these
162
 
    are added.  This search proceeds recursively into versioned
163
 
    directories.
164
 
 
165
 
    Therefore simply saying 'bzr add .' will version all files that
166
 
    are currently unknown.
167
 
 
168
 
    TODO: Perhaps adding a file whose directly is not versioned should
169
 
    recursively add that parent, rather than giving an error?
170
 
    """
171
 
    takes_args = ['file+']
172
 
    takes_options = ['verbose']
173
 
    
174
 
    def run(self, file_list, verbose=False):
175
 
        bzrlib.add.smart_add(file_list, verbose)
176
 
 
177
 
 
178
 
class cmd_relpath(Command):
179
 
    """Show path of a file relative to root"""
180
 
    takes_args = ['filename']
181
 
    
182
 
    def run(self, filename):
183
 
        print Branch(filename).relpath(filename)
184
 
 
185
 
 
186
 
 
187
 
class cmd_inventory(Command):
188
 
    """Show inventory of the current working copy or a revision."""
189
 
    takes_options = ['revision']
190
 
    
191
 
    def run(self, revision=None):
192
 
        b = Branch('.')
193
 
        if revision == None:
194
 
            inv = b.read_working_inventory()
195
 
        else:
196
 
            inv = b.get_revision_inventory(b.lookup_revision(revision))
197
 
 
198
 
        for path, entry in inv.iter_entries():
199
 
            print '%-50s %s' % (entry.file_id, path)
200
 
 
201
 
 
202
 
class cmd_move(Command):
203
 
    """Move files to a different directory.
204
 
 
205
 
    examples:
206
 
        bzr move *.txt doc
207
 
 
208
 
    The destination must be a versioned directory in the same branch.
209
 
    """
210
 
    takes_args = ['source$', 'dest']
211
 
    def run(self, source_list, dest):
212
 
        b = Branch('.')
213
 
 
214
 
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
215
 
 
216
 
 
217
 
class cmd_rename(Command):
218
 
    """Change the name of an entry.
219
 
 
220
 
    examples:
221
 
      bzr rename frob.c frobber.c
222
 
      bzr rename src/frob.c lib/frob.c
223
 
 
224
 
    It is an error if the destination name exists.
225
 
 
226
 
    See also the 'move' command, which moves files into a different
227
 
    directory without changing their name.
228
 
 
229
 
    TODO: Some way to rename multiple files without invoking bzr for each
230
 
    one?"""
231
 
    takes_args = ['from_name', 'to_name']
232
 
    
233
 
    def run(self, from_name, to_name):
234
 
        b = Branch('.')
235
 
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
236
 
 
237
 
 
238
 
 
239
 
class cmd_renames(Command):
240
 
    """Show list of renamed files.
241
 
 
242
 
    TODO: Option to show renames between two historical versions.
243
 
 
244
 
    TODO: Only show renames under dir, rather than in the whole branch.
245
 
    """
246
 
    takes_args = ['dir?']
247
 
 
248
 
    def run(self, dir='.'):
249
 
        b = Branch(dir)
250
 
        old_inv = b.basis_tree().inventory
251
 
        new_inv = b.read_working_inventory()
252
 
 
253
 
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
254
 
        renames.sort()
255
 
        for old_name, new_name in renames:
256
 
            print "%s => %s" % (old_name, new_name)        
257
 
 
258
 
 
259
 
class cmd_info(Command):
260
 
    """Show statistical information for this branch"""
261
 
    def run(self):
262
 
        import info
263
 
        info.show_info(Branch('.'))        
264
 
 
265
 
 
266
 
class cmd_remove(Command):
267
 
    """Make a file unversioned.
268
 
 
269
 
    This makes bzr stop tracking changes to a versioned file.  It does
270
 
    not delete the working copy.
271
 
    """
272
 
    takes_args = ['file+']
273
 
    takes_options = ['verbose']
274
 
    
275
 
    def run(self, file_list, verbose=False):
276
 
        b = Branch(file_list[0])
277
 
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
278
 
 
279
 
 
280
 
class cmd_file_id(Command):
281
 
    """Print file_id of a particular file or directory.
282
 
 
283
 
    The file_id is assigned when the file is first added and remains the
284
 
    same through all revisions where the file exists, even when it is
285
 
    moved or renamed.
286
 
    """
287
 
    hidden = True
288
 
    takes_args = ['filename']
289
 
    def run(self, filename):
290
 
        b = Branch(filename)
291
 
        i = b.inventory.path2id(b.relpath(filename))
292
 
        if i == None:
293
 
            bailout("%r is not a versioned file" % filename)
294
 
        else:
295
 
            print i
296
 
 
297
 
 
298
 
class cmd_file_path(Command):
299
 
    """Print path of file_ids to a file or directory.
300
 
 
301
 
    This prints one line for each directory down to the target,
302
 
    starting at the branch root."""
303
 
    hidden = True
304
 
    takes_args = ['filename']
305
 
    def run(self, filename):
306
 
        b = Branch(filename)
307
 
        inv = b.inventory
308
 
        fid = inv.path2id(b.relpath(filename))
309
 
        if fid == None:
310
 
            bailout("%r is not a versioned file" % filename)
311
 
        for fip in inv.get_idpath(fid):
312
 
            print fip
313
 
 
314
 
 
315
 
class cmd_revision_history(Command):
316
 
    """Display list of revision ids on this branch."""
317
 
    def run(self):
318
 
        for patchid in Branch('.').revision_history():
319
 
            print patchid
320
 
 
321
 
 
322
 
class cmd_directories(Command):
323
 
    """Display list of versioned directories in this branch."""
324
 
    def run(self):
325
 
        for name, ie in Branch('.').read_working_inventory().directories():
326
 
            if name == '':
327
 
                print '.'
328
 
            else:
329
 
                print name
330
 
 
331
 
 
332
 
class cmd_init(Command):
333
 
    """Make a directory into a versioned branch.
334
 
 
335
 
    Use this to create an empty branch, or before importing an
336
 
    existing project.
337
 
 
338
 
    Recipe for importing a tree of files:
339
 
        cd ~/project
340
 
        bzr init
341
 
        bzr add -v .
342
 
        bzr status
343
 
        bzr commit -m 'imported project'
344
 
    """
345
 
    def run(self):
346
 
        Branch('.', init=True)
347
 
 
348
 
 
349
 
class cmd_diff(Command):
350
 
    """Show differences in working tree.
351
 
    
352
 
    If files are listed, only the changes in those files are listed.
353
 
    Otherwise, all changes for the tree are listed.
354
 
 
355
 
    TODO: Given two revision arguments, show the difference between them.
356
 
 
357
 
    TODO: Allow diff across branches.
358
 
 
359
 
    TODO: Option to use external diff command; could be GNU diff, wdiff,
360
 
          or a graphical diff.
361
 
 
362
 
    TODO: Python difflib is not exactly the same as unidiff; should
363
 
          either fix it up or prefer to use an external diff.
364
 
 
365
 
    TODO: If a directory is given, diff everything under that.
366
 
 
367
 
    TODO: Selected-file diff is inefficient and doesn't show you
368
 
          deleted files.
369
 
 
370
 
    TODO: This probably handles non-Unix newlines poorly.
371
 
    """
372
 
    
373
 
    takes_args = ['file*']
374
 
    takes_options = ['revision']
375
 
    aliases = ['di']
376
 
 
377
 
    def run(self, revision=None, file_list=None):
378
 
        from bzrlib.diff import show_diff
379
 
    
380
 
        show_diff(Branch('.'), revision, file_list)
381
 
 
382
 
 
383
 
class cmd_deleted(Command):
384
 
    """List files deleted in the working tree.
385
 
 
386
 
    TODO: Show files deleted since a previous revision, or between two revisions.
387
 
    """
388
 
    def run(self, show_ids=False):
389
 
        b = Branch('.')
390
 
        old = b.basis_tree()
391
 
        new = b.working_tree()
392
 
 
393
 
        ## TODO: Much more efficient way to do this: read in new
394
 
        ## directories with readdir, rather than stating each one.  Same
395
 
        ## level of effort but possibly much less IO.  (Or possibly not,
396
 
        ## if the directories are very large...)
397
 
 
398
 
        for path, ie in old.inventory.iter_entries():
399
 
            if not new.has_id(ie.file_id):
400
 
                if show_ids:
401
 
                    print '%-50s %s' % (path, ie.file_id)
402
 
                else:
403
 
                    print path
404
 
 
405
 
class cmd_root(Command):
406
 
    """Show the tree root directory.
407
 
 
408
 
    The root is the nearest enclosing directory with a .bzr control
409
 
    directory."""
410
 
    takes_args = ['filename?']
411
 
    def run(self, filename=None):
412
 
        """Print the branch root."""
413
 
        print bzrlib.branch.find_branch_root(filename)
414
 
 
415
 
 
416
 
 
417
 
class cmd_log(Command):
418
 
    """Show log of this branch.
419
 
 
420
 
    TODO: Option to limit range.
421
 
 
422
 
    TODO: Perhaps show most-recent first with an option for last.
423
 
    """
424
 
    takes_args = ['filename?']
425
 
    takes_options = ['timezone', 'verbose', 'show-ids']
426
 
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
427
 
        b = Branch((filename or '.'), lock_mode='r')
428
 
        if filename:
429
 
            filename = b.relpath(filename)
430
 
        bzrlib.show_log(b, filename,
431
 
                        show_timezone=timezone,
432
 
                        verbose=verbose,
433
 
                        show_ids=show_ids)
434
 
 
435
 
 
436
 
 
437
 
class cmd_touching_revisions(Command):
438
 
    """Return revision-ids which affected a particular file."""
439
 
    hidden = True
440
 
    takes_args = ["filename"]
441
 
    def run(self, filename):
442
 
        b = Branch(filename, lock_mode='r')
443
 
        inv = b.read_working_inventory()
444
 
        file_id = inv.path2id(b.relpath(filename))
445
 
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
446
 
            print "%6d %s" % (revno, what)
447
 
 
448
 
 
449
 
class cmd_ls(Command):
450
 
    """List files in a tree.
451
 
 
452
 
    TODO: Take a revision or remote path and list that tree instead.
453
 
    """
454
 
    hidden = True
455
 
    def run(self, revision=None, verbose=False):
456
 
        b = Branch('.')
457
 
        if revision == None:
458
 
            tree = b.working_tree()
459
 
        else:
460
 
            tree = b.revision_tree(b.lookup_revision(revision))
461
 
 
462
 
        for fp, fc, kind, fid in tree.list_files():
463
 
            if verbose:
464
 
                if kind == 'directory':
465
 
                    kindch = '/'
466
 
                elif kind == 'file':
467
 
                    kindch = ''
468
 
                else:
469
 
                    kindch = '???'
470
 
 
471
 
                print '%-8s %s%s' % (fc, fp, kindch)
472
 
            else:
473
 
                print fp
474
 
 
475
 
 
476
 
 
477
 
class cmd_unknowns(Command):
478
 
    """List unknown files"""
479
 
    def run(self):
480
 
        for f in Branch('.').unknowns():
481
 
            print quotefn(f)
482
 
 
483
 
 
484
 
 
485
 
class cmd_ignore(Command):
486
 
    """Ignore a command or pattern"""
487
 
    takes_args = ['name_pattern']
488
 
    
489
 
    def run(self, name_pattern):
490
 
        b = Branch('.')
491
 
 
492
 
        # XXX: This will fail if it's a hardlink; should use an AtomicFile class.
493
 
        f = open(b.abspath('.bzrignore'), 'at')
494
 
        f.write(name_pattern + '\n')
495
 
        f.close()
496
 
 
497
 
        inv = b.working_tree().inventory
498
 
        if inv.path2id('.bzrignore'):
499
 
            mutter('.bzrignore is already versioned')
500
 
        else:
501
 
            mutter('need to make new .bzrignore file versioned')
502
 
            b.add(['.bzrignore'])
503
 
 
504
 
 
505
 
 
506
 
class cmd_ignored(Command):
507
 
    """List ignored files and the patterns that matched them."""
508
 
    def run(self):
509
 
        tree = Branch('.').working_tree()
510
 
        for path, file_class, kind, file_id in tree.list_files():
511
 
            if file_class != 'I':
512
 
                continue
513
 
            ## XXX: Slightly inefficient since this was already calculated
514
 
            pat = tree.is_ignored(path)
515
 
            print '%-50s %s' % (path, pat)
516
 
 
517
 
 
518
 
class cmd_lookup_revision(Command):
519
 
    """Lookup the revision-id from a revision-number
520
 
 
521
 
    example:
522
 
        bzr lookup-revision 33
523
 
        """
524
 
    hidden = True
525
 
    takes_args = ['revno']
526
 
    
527
 
    def run(self, revno):
528
 
        try:
529
 
            revno = int(revno)
530
 
        except ValueError:
531
 
            raise BzrCommandError("not a valid revision-number: %r" % revno)
532
 
 
533
 
        print Branch('.').lookup_revision(revno)
534
 
 
535
 
 
536
 
class cmd_export(Command):
537
 
    """Export past revision to destination directory.
538
 
 
539
 
    If no revision is specified this exports the last committed revision."""
540
 
    takes_args = ['dest']
541
 
    takes_options = ['revision']
542
 
    def run(self, dest, revision=None):
543
 
        b = Branch('.')
544
 
        if revision == None:
545
 
            rh = b.revision_history()[-1]
546
 
        else:
547
 
            rh = b.lookup_revision(int(revision))
548
 
        t = b.revision_tree(rh)
549
 
        t.export(dest)
550
 
 
551
 
 
552
 
class cmd_cat(Command):
553
 
    """Write a file's text from a previous revision."""
554
 
 
555
 
    takes_options = ['revision']
556
 
    takes_args = ['filename']
557
 
 
558
 
    def run(self, filename, revision=None):
559
 
        if revision == None:
560
 
            raise BzrCommandError("bzr cat requires a revision number")
561
 
        b = Branch('.')
562
 
        b.print_file(b.relpath(filename), int(revision))
563
 
 
564
 
 
565
 
class cmd_local_time_offset(Command):
566
 
    """Show the offset in seconds from GMT to local time."""
567
 
    hidden = True    
568
 
    def run(self):
569
 
        print bzrlib.osutils.local_time_offset()
570
 
 
571
 
 
572
 
 
573
 
class cmd_commit(Command):
574
 
    """Commit changes into a new revision.
575
 
 
576
 
    TODO: Commit only selected files.
577
 
 
578
 
    TODO: Run hooks on tree to-be-committed, and after commit.
579
 
 
580
 
    TODO: Strict commit that fails if there are unknown or deleted files.
581
 
    """
582
 
    takes_options = ['message', 'file', 'verbose']
583
 
    aliases = ['ci', 'checkin']
584
 
 
585
 
    def run(self, message=None, file=None, verbose=False):
586
 
        ## Warning: shadows builtin file()
587
 
        if not message and not file:
588
 
            raise BzrCommandError("please specify a commit message",
589
 
                                  ["use either --message or --file"])
590
 
        elif message and file:
591
 
            raise BzrCommandError("please specify either --message or --file")
592
 
        
593
 
        if file:
594
 
            import codecs
595
 
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
596
 
 
597
 
        Branch('.').commit(message, verbose=verbose)
598
 
 
599
 
 
600
 
class cmd_check(Command):
601
 
    """Validate consistency of branch history.
602
 
 
603
 
    This command checks various invariants about the branch storage to
604
 
    detect data corruption or bzr bugs.
605
 
    """
606
 
    takes_args = ['dir?']
607
 
    def run(self, dir='.'):
608
 
        import bzrlib.check
609
 
        bzrlib.check.check(Branch(dir, find_root=False))
610
 
 
611
 
 
612
 
 
613
 
class cmd_whoami(Command):
614
 
    """Show bzr user id."""
615
 
    takes_options = ['email']
616
 
    
617
 
    def run(self, email=False):
618
 
        if email:
619
 
            print bzrlib.osutils.user_email()
620
 
        else:
621
 
            print bzrlib.osutils.username()
622
 
 
623
 
 
624
 
class cmd_selftest(Command):
625
 
    """Run internal test suite"""
626
 
    hidden = True
627
 
    def run(self):
628
 
        failures, tests = 0, 0
629
 
 
630
 
        import doctest, bzrlib.store, bzrlib.tests
631
 
        bzrlib.trace.verbose = False
632
 
 
633
 
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
634
 
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
635
 
            mf, mt = doctest.testmod(m)
636
 
            failures += mf
637
 
            tests += mt
638
 
            print '%-40s %3d tests' % (m.__name__, mt),
639
 
            if mf:
640
 
                print '%3d FAILED!' % mf
641
 
            else:
642
 
                print
643
 
 
644
 
        print '%-40s %3d tests' % ('total', tests),
645
 
        if failures:
646
 
            print '%3d FAILED!' % failures
647
 
        else:
648
 
            print
649
 
 
650
 
 
651
 
 
652
 
class cmd_version(Command):
653
 
    """Show version of bzr"""
654
 
    def run(self):
655
 
        show_version()
656
 
 
657
 
def show_version():
658
 
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
659
 
    print bzrlib.__copyright__
660
 
    print "http://bazaar-ng.org/"
661
 
    print
662
 
    print "bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and"
663
 
    print "you may use, modify and redistribute it under the terms of the GNU"
664
 
    print "General Public License version 2 or later."
665
 
 
666
 
 
667
 
class cmd_rocks(Command):
668
 
    """Statement of optimism."""
669
 
    hidden = True
670
 
    def run(self):
671
 
        print "it sure does!"
672
 
 
673
 
 
674
 
class cmd_assert_fail(Command):
675
 
    """Test reporting of assertion failures"""
676
 
    hidden = True
677
 
    def run(self):
678
 
        assert False, "always fails"
679
 
 
680
 
 
681
 
class cmd_help(Command):
682
 
    """Show help on a command or other topic.
683
 
 
684
 
    For a list of all available commands, say 'bzr help commands'."""
685
 
    takes_args = ['topic?']
686
 
    aliases = ['?']
687
 
    
688
 
    def run(self, topic=None):
689
 
        import help
690
 
        help.help(topic)
691
 
 
692
 
 
693
 
######################################################################
694
 
# main routine
695
 
 
696
 
 
697
 
# list of all available options; the rhs can be either None for an
698
 
# option that takes no argument, or a constructor function that checks
699
 
# the type.
700
 
OPTIONS = {
701
 
    'all':                    None,
702
 
    'help':                   None,
703
 
    'file':                   unicode,
704
 
    'message':                unicode,
705
 
    'profile':                None,
706
 
    'revision':               int,
707
 
    'show-ids':               None,
708
 
    'timezone':               str,
709
 
    'verbose':                None,
710
 
    'version':                None,
711
 
    'email':                  None,
712
 
    }
713
 
 
714
 
SHORT_OPTIONS = {
715
 
    'm':                      'message',
716
 
    'F':                      'file', 
717
 
    'r':                      'revision',
718
 
    'v':                      'verbose',
719
 
}
720
 
 
721
 
 
722
 
def parse_args(argv):
723
 
    """Parse command line.
724
 
    
725
 
    Arguments and options are parsed at this level before being passed
726
 
    down to specific command handlers.  This routine knows, from a
727
 
    lookup table, something about the available options, what optargs
728
 
    they take, and which commands will accept them.
729
 
 
730
 
    >>> parse_args('--help'.split())
731
 
    ([], {'help': True})
732
 
    >>> parse_args('--version'.split())
733
 
    ([], {'version': True})
734
 
    >>> parse_args('status --all'.split())
735
 
    (['status'], {'all': True})
736
 
    >>> parse_args('commit --message=biter'.split())
737
 
    (['commit'], {'message': u'biter'})
738
 
    """
739
 
    args = []
740
 
    opts = {}
741
 
 
742
 
    # TODO: Maybe handle '--' to end options?
743
 
 
744
 
    while argv:
745
 
        a = argv.pop(0)
746
 
        if a[0] == '-':
747
 
            # option names must not be unicode
748
 
            a = str(a)
749
 
            optarg = None
750
 
            if a[1] == '-':
751
 
                mutter("  got option %r" % a)
752
 
                if '=' in a:
753
 
                    optname, optarg = a[2:].split('=', 1)
754
 
                else:
755
 
                    optname = a[2:]
756
 
                if optname not in OPTIONS:
757
 
                    bailout('unknown long option %r' % a)
758
 
            else:
759
 
                shortopt = a[1:]
760
 
                if shortopt not in SHORT_OPTIONS:
761
 
                    bailout('unknown short option %r' % a)
762
 
                optname = SHORT_OPTIONS[shortopt]
763
 
            
764
 
            if optname in opts:
765
 
                # XXX: Do we ever want to support this, e.g. for -r?
766
 
                bailout('repeated option %r' % a)
767
 
                
768
 
            optargfn = OPTIONS[optname]
769
 
            if optargfn:
770
 
                if optarg == None:
771
 
                    if not argv:
772
 
                        bailout('option %r needs an argument' % a)
773
 
                    else:
774
 
                        optarg = argv.pop(0)
775
 
                opts[optname] = optargfn(optarg)
776
 
            else:
777
 
                if optarg != None:
778
 
                    bailout('option %r takes no argument' % optname)
779
 
                opts[optname] = True
780
 
        else:
781
 
            args.append(a)
782
 
 
783
 
    return args, opts
784
 
 
785
 
 
786
 
 
787
 
 
788
 
def _match_argform(cmd, takes_args, args):
789
 
    argdict = {}
790
 
 
791
 
    # step through args and takes_args, allowing appropriate 0-many matches
792
 
    for ap in takes_args:
793
 
        argname = ap[:-1]
794
 
        if ap[-1] == '?':
795
 
            if args:
796
 
                argdict[argname] = args.pop(0)
797
 
        elif ap[-1] == '*': # all remaining arguments
798
 
            if args:
799
 
                argdict[argname + '_list'] = args[:]
800
 
                args = []
801
 
            else:
802
 
                argdict[argname + '_list'] = None
803
 
        elif ap[-1] == '+':
804
 
            if not args:
805
 
                raise BzrCommandError("command %r needs one or more %s"
806
 
                        % (cmd, argname.upper()))
807
 
            else:
808
 
                argdict[argname + '_list'] = args[:]
809
 
                args = []
810
 
        elif ap[-1] == '$': # all but one
811
 
            if len(args) < 2:
812
 
                raise BzrCommandError("command %r needs one or more %s"
813
 
                        % (cmd, argname.upper()))
814
 
            argdict[argname + '_list'] = args[:-1]
815
 
            args[:-1] = []                
816
 
        else:
817
 
            # just a plain arg
818
 
            argname = ap
819
 
            if not args:
820
 
                raise BzrCommandError("command %r requires argument %s"
821
 
                        % (cmd, argname.upper()))
822
 
            else:
823
 
                argdict[argname] = args.pop(0)
824
 
            
825
 
    if args:
826
 
        raise BzrCommandError("extra argument to command %s: %s"
827
 
                              % (cmd, args[0]))
828
 
 
829
 
    return argdict
830
 
 
831
 
 
832
 
 
833
 
def run_bzr(argv):
834
 
    """Execute a command.
835
 
 
836
 
    This is similar to main(), but without all the trappings for
837
 
    logging and error handling.  
838
 
    """
839
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
840
 
    
841
 
    try:
842
 
        args, opts = parse_args(argv[1:])
843
 
        if 'help' in opts:
844
 
            import help
845
 
            if args:
846
 
                help.help(args[0])
847
 
            else:
848
 
                help.help()
849
 
            return 0
850
 
        elif 'version' in opts:
851
 
            show_version()
852
 
            return 0
853
 
        cmd = str(args.pop(0))
854
 
    except IndexError:
855
 
        log_error('usage: bzr COMMAND')
856
 
        log_error('  try "bzr help"')
857
 
        return 1
858
 
 
859
 
    canonical_cmd, cmd_class = get_cmd_class(cmd)
860
 
 
861
 
    # global option
862
 
    if 'profile' in opts:
863
 
        profile = True
864
 
        del opts['profile']
865
 
    else:
866
 
        profile = False
867
 
 
868
 
    # check options are reasonable
869
 
    allowed = cmd_class.takes_options
870
 
    for oname in opts:
871
 
        if oname not in allowed:
872
 
            raise BzrCommandError("option '--%s' is not allowed for command %r"
873
 
                                  % (oname, cmd))
874
 
 
875
 
    # mix arguments and options into one dictionary
876
 
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
877
 
    cmdopts = {}
878
 
    for k, v in opts.items():
879
 
        cmdopts[k.replace('-', '_')] = v
880
 
 
881
 
    if profile:
882
 
        import hotshot, tempfile
883
 
        pffileno, pfname = tempfile.mkstemp()
884
 
        try:
885
 
            prof = hotshot.Profile(pfname)
886
 
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
887
 
            prof.close()
888
 
 
889
 
            import hotshot.stats
890
 
            stats = hotshot.stats.load(pfname)
891
 
            #stats.strip_dirs()
892
 
            stats.sort_stats('time')
893
 
            ## XXX: Might like to write to stderr or the trace file instead but
894
 
            ## print_stats seems hardcoded to stdout
895
 
            stats.print_stats(20)
896
 
            
897
 
            return ret.status
898
 
 
899
 
        finally:
900
 
            os.close(pffileno)
901
 
            os.remove(pfname)
902
 
    else:
903
 
        cmdobj = cmd_class(cmdopts, cmdargs).status 
904
 
 
905
 
 
906
 
def _report_exception(summary, quiet=False):
907
 
    import traceback
908
 
    log_error('bzr: ' + summary)
909
 
    bzrlib.trace.log_exception()
910
 
 
911
 
    if not quiet:
912
 
        tb = sys.exc_info()[2]
913
 
        exinfo = traceback.extract_tb(tb)
914
 
        if exinfo:
915
 
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
916
 
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
917
 
 
918
 
 
919
 
 
920
 
def main(argv):
921
 
    import errno
922
 
    
923
 
    bzrlib.open_tracefile(argv)
924
 
 
925
 
    try:
926
 
        try:
927
 
            try:
928
 
                return run_bzr(argv)
929
 
            finally:
930
 
                # do this here inside the exception wrappers to catch EPIPE
931
 
                sys.stdout.flush()
932
 
        except BzrError, e:
933
 
            quiet = isinstance(e, (BzrCommandError))
934
 
            _report_exception('error: ' + e.args[0], quiet=quiet)
935
 
            if len(e.args) > 1:
936
 
                for h in e.args[1]:
937
 
                    # some explanation or hints
938
 
                    log_error('  ' + h)
939
 
            return 1
940
 
        except AssertionError, e:
941
 
            msg = 'assertion failed'
942
 
            if str(e):
943
 
                msg += ': ' + str(e)
944
 
            _report_exception(msg)
945
 
            return 2
946
 
        except KeyboardInterrupt, e:
947
 
            _report_exception('interrupted', quiet=True)
948
 
            return 2
949
 
        except Exception, e:
950
 
            quiet = False
951
 
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
952
 
                quiet = True
953
 
                msg = 'broken pipe'
954
 
            else:
955
 
                msg = str(e).rstrip('\n')
956
 
            _report_exception(msg, quiet)
957
 
            return 2
958
 
    finally:
959
 
        bzrlib.trace.close_trace()
960
 
 
961
 
 
962
 
if __name__ == '__main__':
963
 
    sys.exit(main(sys.argv))