/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: Martin Pool
  • Date: 2005-05-05 02:43:47 UTC
  • Revision ID: mbp@sourcefrog.net-20050505024347-8cc27a7a75e4d9ea
- add-bzr-to-baz utility script

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.open_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))