/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

Partially fix pull.

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