~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-19 08:31:06 UTC
  • Revision ID: mbp@sourcefrog.net-20050519083106-ebe71562d3bda4a7
- fix typo

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
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, types, shutil, tempfile, fnmatch, difflib, os.path
68
 
from sets import Set
69
 
from pprint import pprint
70
 
from stat import *
71
 
from glob import glob
 
17
 
 
18
 
 
19
import sys, os, time, os.path
72
20
 
73
21
import bzrlib
74
 
from bzrlib.store import ImmutableStore
75
22
from bzrlib.trace import mutter, note, log_error
76
23
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
77
24
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
78
 
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
25
from bzrlib.tree import RevisionTree, EmptyTree, Tree
79
26
from bzrlib.revision import Revision
80
27
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
81
28
     format_date
82
 
 
83
 
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
84
 
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
85
 
 
86
 
## standard representation
87
 
NONE_STRING = '(none)'
88
 
EMPTY = 'empty'
89
 
 
90
 
 
91
 
CMD_ALIASES = {
92
 
    '?':         'help',
93
 
    'ci':        'commit',
94
 
    'checkin':   'commit',
95
 
    'di':        'diff',
96
 
    'st':        'status',
97
 
    'stat':      'status',
98
 
    }
99
 
 
 
29
from bzrlib import merge
 
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
100
45
 
101
46
def get_cmd_class(cmd):
102
 
    cmd = str(cmd)
103
 
    
104
 
    cmd = CMD_ALIASES.get(cmd, cmd)
105
 
    
 
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
106
52
    try:
107
 
        cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
 
53
        return cmd, globals()[_squish_command_name(cmd)]
108
54
    except KeyError:
109
 
        raise BzrError("unknown command %r" % cmd)
110
 
 
111
 
    return cmd, cmd_class
112
 
 
 
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
 
 
62
    cmdclass = ExternalCommand.find_command(cmd)
 
63
    if cmdclass:
 
64
        return cmd, cmdclass
 
65
 
 
66
    raise BzrCommandError("unknown command %r" % cmd)
113
67
 
114
68
 
115
69
class Command:
155
109
        This is invoked with the options and arguments bound to
156
110
        keyword parameters.
157
111
 
158
 
        Return True if the command was successful, False if not.
 
112
        Return 0 or None if the command was successful, or a shell
 
113
        error code if not.
159
114
        """
160
 
        return True
161
 
 
 
115
        return 0
 
116
 
 
117
 
 
118
class ExternalCommand(Command):
 
119
    """Class to wrap external commands.
 
120
 
 
121
    We cheat a little here, when get_cmd_class() calls us we actually give it back
 
122
    an object we construct that has the appropriate path, help, options etc for the
 
123
    specified command.
 
124
 
 
125
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
 
126
    method, which we override to call the Command.__init__ method. That then calls
 
127
    our run method which is pretty straight forward.
 
128
 
 
129
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
 
130
    back into command line options and arguments for the script.
 
131
    """
 
132
 
 
133
    def find_command(cls, cmd):
 
134
        bzrpath = os.environ.get('BZRPATH', '')
 
135
 
 
136
        for dir in bzrpath.split(':'):
 
137
            path = os.path.join(dir, cmd)
 
138
            if os.path.isfile(path):
 
139
                return ExternalCommand(path)
 
140
 
 
141
        return None
 
142
 
 
143
    find_command = classmethod(find_command)
 
144
 
 
145
    def __init__(self, path):
 
146
        self.path = path
 
147
 
 
148
        # TODO: If either of these fail, we should detect that and
 
149
        # assume that path is not really a bzr plugin after all.
 
150
 
 
151
        pipe = os.popen('%s --bzr-usage' % path, 'r')
 
152
        self.takes_options = pipe.readline().split()
 
153
        self.takes_args = pipe.readline().split()
 
154
        pipe.close()
 
155
 
 
156
        pipe = os.popen('%s --bzr-help' % path, 'r')
 
157
        self.__doc__ = pipe.read()
 
158
        pipe.close()
 
159
 
 
160
    def __call__(self, options, arguments):
 
161
        Command.__init__(self, options, arguments)
 
162
        return self
 
163
 
 
164
    def run(self, **kargs):
 
165
        opts = []
 
166
        args = []
 
167
 
 
168
        keys = kargs.keys()
 
169
        keys.sort()
 
170
        for name in keys:
 
171
            value = kargs[name]
 
172
            if OPTIONS.has_key(name):
 
173
                # it's an option
 
174
                opts.append('--%s' % name)
 
175
                if value is not None and value is not True:
 
176
                    opts.append(str(value))
 
177
            else:
 
178
                # it's an arg, or arg list
 
179
                if type(value) is not list:
 
180
                    value = [value]
 
181
                for v in value:
 
182
                    if v is not None:
 
183
                        args.append(str(v))
 
184
 
 
185
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
 
186
        return self.status
162
187
 
163
188
 
164
189
class cmd_status(Command):
165
190
    """Display status summary.
166
191
 
167
 
    For each file there is a single line giving its file state and name.
168
 
    The name is that in the current revision unless it is deleted or
169
 
    missing, in which case the old name is shown.
 
192
    This reports on versioned and unknown files, reporting them
 
193
    grouped by state.  Possible states are:
 
194
 
 
195
    added
 
196
        Versioned in the working copy but not in the previous revision.
 
197
 
 
198
    removed
 
199
        Versioned in the previous revision but removed or deleted
 
200
        in the working copy.
 
201
 
 
202
    renamed
 
203
        Path of this file changed from the previous revision;
 
204
        the text may also have changed.  This includes files whose
 
205
        parent directory was renamed.
 
206
 
 
207
    modified
 
208
        Text has changed since the previous revision.
 
209
 
 
210
    unchanged
 
211
        Nothing about this file has changed since the previous revision.
 
212
        Only shown with --all.
 
213
 
 
214
    unknown
 
215
        Not versioned and not matching an ignore pattern.
 
216
 
 
217
    To see ignored files use 'bzr ignored'.  For details in the
 
218
    changes to file texts, use 'bzr diff'.
 
219
 
 
220
    If no arguments are specified, the status of the entire working
 
221
    directory is shown.  Otherwise, only the status of the specified
 
222
    files or directories is reported.  If a directory is given, status
 
223
    is reported for everything inside that directory.
170
224
    """
171
 
    takes_options = ['all']
 
225
    takes_args = ['file*']
 
226
    takes_options = ['all', 'show-ids']
 
227
    aliases = ['st', 'stat']
172
228
    
173
 
    def run(self, all=False):
174
 
        #import bzrlib.status
175
 
        #bzrlib.status.tree_status(Branch('.'))
176
 
        Branch('.').show_status(show_all=all)
 
229
    def run(self, all=False, show_ids=False, file_list=None):
 
230
        if file_list:
 
231
            b = Branch(file_list[0], lock_mode='r')
 
232
            file_list = [b.relpath(x) for x in file_list]
 
233
            # special case: only one path was given and it's the root
 
234
            # of the branch
 
235
            if file_list == ['']:
 
236
                file_list = None
 
237
        else:
 
238
            b = Branch('.', lock_mode='r')
 
239
        import status
 
240
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
 
241
                           specific_files=file_list)
177
242
 
178
243
 
179
244
class cmd_cat_revision(Command):
222
287
        bzrlib.add.smart_add(file_list, verbose)
223
288
 
224
289
 
225
 
def Relpath(Command):
 
290
class cmd_relpath(Command):
226
291
    """Show path of a file relative to root"""
227
 
    takes_args = ('filename')
 
292
    takes_args = ['filename']
228
293
    
229
 
    def run(self):
230
 
        print Branch(self.args['filename']).relpath(filename)
 
294
    def run(self, filename):
 
295
        print Branch(filename).relpath(filename)
231
296
 
232
297
 
233
298
 
304
369
 
305
370
 
306
371
class cmd_info(Command):
307
 
    """Show statistical information for this branch"""
308
 
    def run(self):
 
372
    """Show statistical information about a branch."""
 
373
    takes_args = ['branch?']
 
374
    
 
375
    def run(self, branch=None):
309
376
        import info
310
 
        info.show_info(Branch('.'))        
 
377
 
 
378
        from branch import find_branch
 
379
        b = find_branch(branch)
 
380
        info.show_info(b)
311
381
 
312
382
 
313
383
class cmd_remove(Command):
419
489
    
420
490
    takes_args = ['file*']
421
491
    takes_options = ['revision']
 
492
    aliases = ['di']
422
493
 
423
494
    def run(self, revision=None, file_list=None):
424
495
        from bzrlib.diff import show_diff
425
496
    
426
 
        show_diff(Branch('.'), revision, file_list)
 
497
        show_diff(Branch('.'), revision, specific_files=file_list)
 
498
 
 
499
 
 
500
        
427
501
 
428
502
 
429
503
class cmd_deleted(Command):
448
522
                else:
449
523
                    print path
450
524
 
 
525
 
 
526
class cmd_modified(Command):
 
527
    """List files modified in working tree."""
 
528
    hidden = True
 
529
    def run(self):
 
530
        import statcache
 
531
        b = Branch('.')
 
532
        inv = b.read_working_inventory()
 
533
        sc = statcache.update_cache(b, inv)
 
534
        basis = b.basis_tree()
 
535
        basis_inv = basis.inventory
 
536
        
 
537
        # We used to do this through iter_entries(), but that's slow
 
538
        # when most of the files are unmodified, as is usually the
 
539
        # case.  So instead we iterate by inventory entry, and only
 
540
        # calculate paths as necessary.
 
541
 
 
542
        for file_id in basis_inv:
 
543
            cacheentry = sc.get(file_id)
 
544
            if not cacheentry:                 # deleted
 
545
                continue
 
546
            ie = basis_inv[file_id]
 
547
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
 
548
                path = inv.id2path(file_id)
 
549
                print path
 
550
 
 
551
 
 
552
 
 
553
class cmd_added(Command):
 
554
    """List files added in working tree."""
 
555
    hidden = True
 
556
    def run(self):
 
557
        b = Branch('.')
 
558
        wt = b.working_tree()
 
559
        basis_inv = b.basis_tree().inventory
 
560
        inv = wt.inventory
 
561
        for file_id in inv:
 
562
            if file_id in basis_inv:
 
563
                continue
 
564
            path = inv.id2path(file_id)
 
565
            if not os.access(b.abspath(path), os.F_OK):
 
566
                continue
 
567
            print path
 
568
                
 
569
        
 
570
 
451
571
class cmd_root(Command):
452
572
    """Show the tree root directory.
453
573
 
456
576
    takes_args = ['filename?']
457
577
    def run(self, filename=None):
458
578
        """Print the branch root."""
459
 
        print bzrlib.branch.find_branch_root(filename)
460
 
 
 
579
        from branch import find_branch
 
580
        b = find_branch(filename)
 
581
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
461
582
 
462
583
 
463
584
class cmd_log(Command):
464
585
    """Show log of this branch.
465
586
 
466
 
    TODO: Options to show ids; to limit range; etc.
 
587
    TODO: Option to limit range.
 
588
 
 
589
    TODO: Option to show in forward order.
467
590
    """
468
 
    takes_options = ['timezone', 'verbose']
469
 
    def run(self, timezone='original', verbose=False):
470
 
        Branch('.').write_log(show_timezone=timezone, verbose=verbose)
 
591
    takes_args = ['filename?']
 
592
    takes_options = ['timezone', 'verbose', 'show-ids']
 
593
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
 
594
        from bzrlib import show_log, find_branch
 
595
        
 
596
        if filename:
 
597
            b = find_branch(filename, lock_mode='r')
 
598
            fp = b.relpath(filename)
 
599
            if fp:
 
600
                file_id = b.read_working_inventory().path2id(fp)
 
601
            else:
 
602
                file_id = None  # points to branch root
 
603
        else:
 
604
            b = find_branch('.', lock_mode='r')
 
605
            file_id = None
 
606
 
 
607
        show_log(b, file_id,
 
608
                 show_timezone=timezone,
 
609
                 verbose=verbose,
 
610
                 show_ids=show_ids,
 
611
                 to_file=sys.stdout)
 
612
 
 
613
 
 
614
 
 
615
class cmd_touching_revisions(Command):
 
616
    """Return revision-ids which affected a particular file.
 
617
 
 
618
    A more user-friendly interface is "bzr log FILE"."""
 
619
    hidden = True
 
620
    takes_args = ["filename"]
 
621
    def run(self, filename):
 
622
        b = Branch(filename, lock_mode='r')
 
623
        inv = b.read_working_inventory()
 
624
        file_id = inv.path2id(b.relpath(filename))
 
625
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
 
626
            print "%6d %s" % (revno, what)
471
627
 
472
628
 
473
629
class cmd_ls(Command):
507
663
 
508
664
 
509
665
class cmd_ignore(Command):
510
 
    """Ignore a command or pattern"""
 
666
    """Ignore a command or pattern
 
667
 
 
668
    To remove patterns from the ignore list, edit the .bzrignore file.
 
669
 
 
670
    If the pattern contains a slash, it is compared to the whole path
 
671
    from the branch root.  Otherwise, it is comapred to only the last
 
672
    component of the path.
 
673
 
 
674
    Ignore patterns are case-insensitive on case-insensitive systems.
 
675
 
 
676
    Note: wildcards must be quoted from the shell on Unix.
 
677
 
 
678
    examples:
 
679
        bzr ignore ./Makefile
 
680
        bzr ignore '*.class'
 
681
    """
511
682
    takes_args = ['name_pattern']
512
683
    
513
684
    def run(self, name_pattern):
 
685
        from bzrlib.atomicfile import AtomicFile
 
686
        import codecs
 
687
 
514
688
        b = Branch('.')
515
 
 
516
 
        # XXX: This will fail if it's a hardlink; should use an AtomicFile class.
517
 
        f = open(b.abspath('.bzrignore'), 'at')
518
 
        f.write(name_pattern + '\n')
519
 
        f.close()
 
689
        ifn = b.abspath('.bzrignore')
 
690
 
 
691
        if os.path.exists(ifn):
 
692
            f = open(ifn, 'rt')
 
693
            try:
 
694
                igns = f.read().decode('utf-8')
 
695
            finally:
 
696
                f.close()
 
697
        else:
 
698
            igns = ''
 
699
 
 
700
        if igns and igns[-1] != '\n':
 
701
            igns += '\n'
 
702
        igns += name_pattern + '\n'
 
703
 
 
704
        try:
 
705
            f = AtomicFile(ifn, 'wt')
 
706
            f.write(igns.encode('utf-8'))
 
707
            f.commit()
 
708
        finally:
 
709
            f.close()
520
710
 
521
711
        inv = b.working_tree().inventory
522
712
        if inv.path2id('.bzrignore'):
528
718
 
529
719
 
530
720
class cmd_ignored(Command):
531
 
    """List ignored files and the patterns that matched them."""
 
721
    """List ignored files and the patterns that matched them.
 
722
 
 
723
    See also: bzr ignore"""
532
724
    def run(self):
533
725
        tree = Branch('.').working_tree()
534
726
        for path, file_class, kind, file_id in tree.list_files():
544
736
 
545
737
    example:
546
738
        bzr lookup-revision 33
547
 
        """
 
739
    """
548
740
    hidden = True
 
741
    takes_args = ['revno']
 
742
    
549
743
    def run(self, revno):
550
744
        try:
551
745
            revno = int(revno)
552
746
        except ValueError:
553
 
            raise BzrError("not a valid revision-number: %r" % revno)
554
 
 
555
 
        print Branch('.').lookup_revision(revno) or NONE_STRING
556
 
 
 
747
            raise BzrCommandError("not a valid revision-number: %r" % revno)
 
748
 
 
749
        print Branch('.').lookup_revision(revno)
557
750
 
558
751
 
559
752
class cmd_export(Command):
562
755
    If no revision is specified this exports the last committed revision."""
563
756
    takes_args = ['dest']
564
757
    takes_options = ['revision']
565
 
    def run(self, dest, revno=None):
 
758
    def run(self, dest, revision=None):
566
759
        b = Branch('.')
567
 
        if revno == None:
568
 
            rh = b.revision_history[-1]
 
760
        if revision == None:
 
761
            rh = b.revision_history()[-1]
569
762
        else:
570
 
            rh = b.lookup_revision(int(revno))
 
763
            rh = b.lookup_revision(int(revision))
571
764
        t = b.revision_tree(rh)
572
765
        t.export(dest)
573
766
 
596
789
class cmd_commit(Command):
597
790
    """Commit changes into a new revision.
598
791
 
599
 
    TODO: Commit only selected files.
 
792
    If selected files are specified, only changes to those files are
 
793
    committed.  If a directory is specified then its contents are also
 
794
    committed.
 
795
 
 
796
    A selected-file commit may fail in some cases where the committed
 
797
    tree would be invalid, such as trying to commit a file in a
 
798
    newly-added directory that is not itself committed.
600
799
 
601
800
    TODO: Run hooks on tree to-be-committed, and after commit.
602
801
 
603
802
    TODO: Strict commit that fails if there are unknown or deleted files.
604
803
    """
605
 
    takes_options = ['message', 'verbose']
606
 
    
607
 
    def run(self, message=None, verbose=False):
608
 
        if not message:
609
 
            raise BzrCommandError("please specify a commit message")
610
 
        Branch('.').commit(message, verbose=verbose)
 
804
    takes_args = ['selected*']
 
805
    takes_options = ['message', 'file', 'verbose']
 
806
    aliases = ['ci', 'checkin']
 
807
 
 
808
    def run(self, message=None, file=None, verbose=True, selected_list=None):
 
809
        from bzrlib.commit import commit
 
810
 
 
811
        ## Warning: shadows builtin file()
 
812
        if not message and not file:
 
813
            raise BzrCommandError("please specify a commit message",
 
814
                                  ["use either --message or --file"])
 
815
        elif message and file:
 
816
            raise BzrCommandError("please specify either --message or --file")
 
817
        
 
818
        if file:
 
819
            import codecs
 
820
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
 
821
 
 
822
        b = Branch('.')
 
823
        commit(b, message, verbose=verbose, specific_files=selected_list)
611
824
 
612
825
 
613
826
class cmd_check(Command):
619
832
    takes_args = ['dir?']
620
833
    def run(self, dir='.'):
621
834
        import bzrlib.check
622
 
        bzrlib.check.check(Branch(dir, find_root=False))
 
835
        bzrlib.check.check(Branch(dir))
623
836
 
624
837
 
625
838
 
640
853
    def run(self):
641
854
        failures, tests = 0, 0
642
855
 
643
 
        import doctest, bzrlib.store, bzrlib.tests
 
856
        import doctest, bzrlib.store
644
857
        bzrlib.trace.verbose = False
645
858
 
646
859
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
647
 
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
860
            bzrlib.tree, bzrlib.commands, bzrlib.add:
648
861
            mf, mt = doctest.testmod(m)
649
862
            failures += mf
650
863
            tests += mt
657
870
        print '%-40s %3d tests' % ('total', tests),
658
871
        if failures:
659
872
            print '%3d FAILED!' % failures
 
873
            return 1
660
874
        else:
661
875
            print
 
876
            return 0
662
877
 
663
878
 
664
879
 
683
898
    def run(self):
684
899
        print "it sure does!"
685
900
 
 
901
def parse_spec(spec):
 
902
    if '/@' in spec:
 
903
        parsed = spec.split('/@')
 
904
        assert len(parsed) == 2
 
905
        if parsed[1] == "":
 
906
            parsed[1] = -1
 
907
        else:
 
908
            parsed[1] = int(parsed[1])
 
909
            assert parsed[1] >=0
 
910
    else:
 
911
        parsed = [spec, None]
 
912
    return parsed
 
913
 
 
914
class cmd_merge(Command):
 
915
    """Perform a three-way merge of trees."""
 
916
    takes_args = ['other_spec', 'base_spec']
 
917
 
 
918
    def run(self, other_spec, base_spec):
 
919
        merge.merge(parse_spec(other_spec), parse_spec(base_spec))
686
920
 
687
921
class cmd_assert_fail(Command):
688
922
    """Test reporting of assertion failures"""
696
930
 
697
931
    For a list of all available commands, say 'bzr help commands'."""
698
932
    takes_args = ['topic?']
 
933
    aliases = ['?']
699
934
    
700
935
    def run(self, topic=None):
701
 
        help(topic)
702
 
 
703
 
 
704
 
def help(topic=None):
705
 
    if topic == None:
706
 
        print __doc__
707
 
    elif topic == 'commands':
708
 
        help_commands()
709
 
    else:
710
 
        help_on_command(topic)
711
 
 
712
 
 
713
 
def help_on_command(cmdname):
714
 
    cmdname = str(cmdname)
715
 
 
716
 
    from inspect import getdoc
717
 
    topic, cmdclass = get_cmd_class(cmdname)
718
 
 
719
 
    doc = getdoc(cmdclass)
720
 
    if doc == None:
721
 
        raise NotImplementedError("sorry, no detailed help yet for %r" % cmdname)
722
 
 
723
 
    if '\n' in doc:
724
 
        short, rest = doc.split('\n', 1)
725
 
    else:
726
 
        short = doc
727
 
        rest = ''
728
 
 
729
 
    print 'usage: bzr ' + topic,
730
 
    for aname in cmdclass.takes_args:
731
 
        aname = aname.upper()
732
 
        if aname[-1] in ['$', '+']:
733
 
            aname = aname[:-1] + '...'
734
 
        elif aname[-1] == '?':
735
 
            aname = '[' + aname[:-1] + ']'
736
 
        elif aname[-1] == '*':
737
 
            aname = '[' + aname[:-1] + '...]'
738
 
        print aname,
739
 
    print 
740
 
    print short
741
 
    if rest:
742
 
        print rest
743
 
 
744
 
    help_on_option(cmdclass.takes_options)
745
 
 
746
 
 
747
 
def help_on_option(options):
748
 
    if not options:
749
 
        return
750
 
    
751
 
    print
752
 
    print 'options:'
753
 
    for on in options:
754
 
        l = '    --' + on
755
 
        for shortname, longname in SHORT_OPTIONS.items():
756
 
            if longname == on:
757
 
                l += ', -' + shortname
758
 
                break
759
 
        print l
760
 
 
761
 
 
762
 
def help_commands():
763
 
    """List all commands"""
764
 
    import inspect
765
 
    
766
 
    accu = []
767
 
    for k, v in globals().items():
768
 
        if k.startswith('cmd_'):
769
 
            accu.append((k[4:].replace('_','-'), v))
770
 
    accu.sort()
771
 
    for cmdname, cmdclass in accu:
772
 
        if cmdclass.hidden:
773
 
            continue
774
 
        print cmdname
775
 
        help = inspect.getdoc(cmdclass)
776
 
        if help:
777
 
            print "    " + help.split('\n', 1)[0]
778
 
            
 
936
        import help
 
937
        help.help(topic)
 
938
 
 
939
 
 
940
class cmd_update_stat_cache(Command):
 
941
    """Update stat-cache mapping inodes to SHA-1 hashes.
 
942
 
 
943
    For testing only."""
 
944
    hidden = True
 
945
    def run(self):
 
946
        import statcache
 
947
        b = Branch('.')
 
948
        statcache.update_cache(b.base, b.read_working_inventory())
 
949
 
779
950
 
780
951
######################################################################
781
952
# main routine
787
958
OPTIONS = {
788
959
    'all':                    None,
789
960
    'help':                   None,
 
961
    'file':                   unicode,
790
962
    'message':                unicode,
791
963
    'profile':                None,
792
964
    'revision':               int,
799
971
 
800
972
SHORT_OPTIONS = {
801
973
    'm':                      'message',
 
974
    'F':                      'file', 
802
975
    'r':                      'revision',
803
976
    'v':                      'verbose',
804
977
}
921
1094
    This is similar to main(), but without all the trappings for
922
1095
    logging and error handling.  
923
1096
    """
924
 
 
925
1097
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
926
1098
    
927
1099
    try:
928
1100
        args, opts = parse_args(argv[1:])
929
1101
        if 'help' in opts:
 
1102
            import help
930
1103
            if args:
931
 
                help(args[0])
 
1104
                help.help(args[0])
932
1105
            else:
933
 
                help()
 
1106
                help.help()
934
1107
            return 0
935
1108
        elif 'version' in opts:
936
 
            cmd_version([], [])
 
1109
            show_version()
937
1110
            return 0
938
1111
        cmd = str(args.pop(0))
939
1112
    except IndexError:
940
 
        log_error('usage: bzr COMMAND')
941
 
        log_error('  try "bzr help"')
 
1113
        import help
 
1114
        help.help()
942
1115
        return 1
 
1116
          
943
1117
 
944
1118
    canonical_cmd, cmd_class = get_cmd_class(cmd)
945
1119
 
954
1128
    allowed = cmd_class.takes_options
955
1129
    for oname in opts:
956
1130
        if oname not in allowed:
957
 
            raise BzrCommandError("option %r is not allowed for command %r"
 
1131
            raise BzrCommandError("option '--%s' is not allowed for command %r"
958
1132
                                  % (oname, cmd))
959
1133
 
960
1134
    # mix arguments and options into one dictionary
964
1138
        cmdopts[k.replace('-', '_')] = v
965
1139
 
966
1140
    if profile:
967
 
        import hotshot
 
1141
        import hotshot, tempfile
968
1142
        pffileno, pfname = tempfile.mkstemp()
969
1143
        try:
970
1144
            prof = hotshot.Profile(pfname)
979
1153
            ## print_stats seems hardcoded to stdout
980
1154
            stats.print_stats(20)
981
1155
            
982
 
            return ret
 
1156
            return ret.status
983
1157
 
984
1158
        finally:
985
1159
            os.close(pffileno)
986
1160
            os.remove(pfname)
987
1161
    else:
988
 
        cmdobj = cmd_class(cmdopts, cmdargs) or 0
989
 
 
990
 
 
991
 
 
992
 
def _report_exception(e, summary, quiet=False):
 
1162
        return cmd_class(cmdopts, cmdargs).status 
 
1163
 
 
1164
 
 
1165
def _report_exception(summary, quiet=False):
993
1166
    import traceback
994
1167
    log_error('bzr: ' + summary)
995
 
    bzrlib.trace.log_exception(e)
 
1168
    bzrlib.trace.log_exception()
996
1169
 
997
1170
    if not quiet:
998
1171
        tb = sys.exc_info()[2]
1006
1179
def main(argv):
1007
1180
    import errno
1008
1181
    
1009
 
    bzrlib.trace.create_tracefile(argv)
 
1182
    bzrlib.open_tracefile(argv)
1010
1183
 
1011
1184
    try:
1012
1185
        try:
1013
 
            ret = run_bzr(argv)
1014
 
            # do this here to catch EPIPE
1015
 
            sys.stdout.flush()
1016
 
            return ret
 
1186
            try:
 
1187
                return run_bzr(argv)
 
1188
            finally:
 
1189
                # do this here inside the exception wrappers to catch EPIPE
 
1190
                sys.stdout.flush()
1017
1191
        except BzrError, e:
1018
1192
            quiet = isinstance(e, (BzrCommandError))
1019
 
            _report_exception(e, 'error: ' + e.args[0], quiet=quiet)
 
1193
            _report_exception('error: ' + e.args[0], quiet=quiet)
1020
1194
            if len(e.args) > 1:
1021
1195
                for h in e.args[1]:
1022
1196
                    # some explanation or hints
1026
1200
            msg = 'assertion failed'
1027
1201
            if str(e):
1028
1202
                msg += ': ' + str(e)
1029
 
            _report_exception(e, msg)
 
1203
            _report_exception(msg)
1030
1204
            return 2
1031
1205
        except KeyboardInterrupt, e:
1032
 
            _report_exception(e, 'interrupted', quiet=True)
 
1206
            _report_exception('interrupted', quiet=True)
1033
1207
            return 2
1034
1208
        except Exception, e:
1035
1209
            quiet = False
1036
 
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
 
1210
            if (isinstance(e, IOError) 
 
1211
                and hasattr(e, 'errno')
 
1212
                and e.errno == errno.EPIPE):
1037
1213
                quiet = True
1038
1214
                msg = 'broken pipe'
1039
1215
            else:
1040
1216
                msg = str(e).rstrip('\n')
1041
 
            _report_exception(e, msg, quiet)
 
1217
            _report_exception(msg, quiet)
1042
1218
            return 2
1043
1219
    finally:
1044
1220
        bzrlib.trace.close_trace()