~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-16 02:19:13 UTC
  • Revision ID: mbp@sourcefrog.net-20050516021913-3a933f871079e3fe
- patch from ddaa to create api/ directory 
  before building API docs

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
 
17
 
 
18
 
 
19
import sys, os, time, os.path
68
20
from sets import Set
69
 
from pprint import pprint
70
 
from stat import *
71
 
from glob import glob
72
21
 
73
22
import bzrlib
74
 
from bzrlib.store import ImmutableStore
75
23
from bzrlib.trace import mutter, note, log_error
76
24
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
77
25
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
78
 
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
26
from bzrlib.tree import RevisionTree, EmptyTree, Tree
79
27
from bzrlib.revision import Revision
80
28
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
81
29
     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
 
 
 
30
from bzrlib import merge
 
31
 
 
32
 
 
33
def _squish_command_name(cmd):
 
34
    return 'cmd_' + cmd.replace('-', '_')
 
35
 
 
36
 
 
37
def _unsquish_command_name(cmd):
 
38
    assert cmd.startswith("cmd_")
 
39
    return cmd[4:].replace('_','-')
 
40
 
 
41
def get_all_cmds():
 
42
    """Return canonical name and class for all registered commands."""
 
43
    for k, v in globals().iteritems():
 
44
        if k.startswith("cmd_"):
 
45
            yield _unsquish_command_name(k), v
100
46
 
101
47
def get_cmd_class(cmd):
102
 
    cmd = str(cmd)
103
 
    
104
 
    cmd = CMD_ALIASES.get(cmd, cmd)
105
 
    
 
48
    """Return the canonical name and command class for a command.
 
49
    """
 
50
    cmd = str(cmd)                      # not unicode
 
51
 
 
52
    # first look up this command under the specified name
106
53
    try:
107
 
        cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
 
54
        return cmd, globals()[_squish_command_name(cmd)]
108
55
    except KeyError:
109
 
        raise BzrError("unknown command %r" % cmd)
110
 
 
111
 
    return cmd, cmd_class
112
 
 
 
56
        pass
 
57
 
 
58
    # look for any command which claims this as an alias
 
59
    for cmdname, cmdclass in get_all_cmds():
 
60
        if cmd in cmdclass.aliases:
 
61
            return cmdname, cmdclass
 
62
 
 
63
    cmdclass = ExternalCommand.find_command(cmd)
 
64
    if cmdclass:
 
65
        return cmd, cmdclass
 
66
 
 
67
    raise BzrCommandError("unknown command %r" % cmd)
113
68
 
114
69
 
115
70
class Command:
155
110
        This is invoked with the options and arguments bound to
156
111
        keyword parameters.
157
112
 
158
 
        Return True if the command was successful, False if not.
 
113
        Return 0 or None if the command was successful, or a shell
 
114
        error code if not.
159
115
        """
160
 
        return True
161
 
 
 
116
        return 0
 
117
 
 
118
 
 
119
class ExternalCommand(Command):
 
120
    """Class to wrap external commands.
 
121
 
 
122
    We cheat a little here, when get_cmd_class() calls us we actually give it back
 
123
    an object we construct that has the appropriate path, help, options etc for the
 
124
    specified command.
 
125
 
 
126
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
 
127
    method, which we override to call the Command.__init__ method. That then calls
 
128
    our run method which is pretty straight forward.
 
129
 
 
130
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
 
131
    back into command line options and arguments for the script.
 
132
    """
 
133
 
 
134
    def find_command(cls, cmd):
 
135
        bzrpath = os.environ.get('BZRPATH', '')
 
136
 
 
137
        for dir in bzrpath.split(':'):
 
138
            path = os.path.join(dir, cmd)
 
139
            if os.path.isfile(path):
 
140
                return ExternalCommand(path)
 
141
 
 
142
        return None
 
143
 
 
144
    find_command = classmethod(find_command)
 
145
 
 
146
    def __init__(self, path):
 
147
        self.path = path
 
148
 
 
149
        # TODO: If either of these fail, we should detect that and
 
150
        # assume that path is not really a bzr plugin after all.
 
151
 
 
152
        pipe = os.popen('%s --bzr-usage' % path, 'r')
 
153
        self.takes_options = pipe.readline().split()
 
154
        self.takes_args = pipe.readline().split()
 
155
        pipe.close()
 
156
 
 
157
        pipe = os.popen('%s --bzr-help' % path, 'r')
 
158
        self.__doc__ = pipe.read()
 
159
        pipe.close()
 
160
 
 
161
    def __call__(self, options, arguments):
 
162
        Command.__init__(self, options, arguments)
 
163
        return self
 
164
 
 
165
    def run(self, **kargs):
 
166
        opts = []
 
167
        args = []
 
168
 
 
169
        keys = kargs.keys()
 
170
        keys.sort()
 
171
        for name in keys:
 
172
            value = kargs[name]
 
173
            if OPTIONS.has_key(name):
 
174
                # it's an option
 
175
                opts.append('--%s' % name)
 
176
                if value is not None and value is not True:
 
177
                    opts.append(str(value))
 
178
            else:
 
179
                # it's an arg, or arg list
 
180
                if type(value) is not list:
 
181
                    value = [value]
 
182
                for v in value:
 
183
                    if v is not None:
 
184
                        args.append(str(v))
 
185
 
 
186
        self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
 
187
        return self.status
162
188
 
163
189
 
164
190
class cmd_status(Command):
165
191
    """Display status summary.
166
192
 
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.
 
193
    This reports on versioned and unknown files, reporting them
 
194
    grouped by state.  Possible states are:
 
195
 
 
196
    added
 
197
        Versioned in the working copy but not in the previous revision.
 
198
 
 
199
    removed
 
200
        Versioned in the previous revision but removed or deleted
 
201
        in the working copy.
 
202
 
 
203
    renamed
 
204
        Path of this file changed from the previous revision;
 
205
        the text may also have changed.  This includes files whose
 
206
        parent directory was renamed.
 
207
 
 
208
    modified
 
209
        Text has changed since the previous revision.
 
210
 
 
211
    unchanged
 
212
        Nothing about this file has changed since the previous revision.
 
213
        Only shown with --all.
 
214
 
 
215
    unknown
 
216
        Not versioned and not matching an ignore pattern.
 
217
 
 
218
    To see ignored files use 'bzr ignored'.  For details in the
 
219
    changes to file texts, use 'bzr diff'.
 
220
 
 
221
    If no arguments are specified, the status of the entire working
 
222
    directory is shown.  Otherwise, only the status of the specified
 
223
    files or directories is reported.  If a directory is given, status
 
224
    is reported for everything inside that directory.
170
225
    """
171
 
    takes_options = ['all']
 
226
    takes_args = ['file*']
 
227
    takes_options = ['all', 'show-ids']
 
228
    aliases = ['st', 'stat']
172
229
    
173
 
    def run(self, all=False):
174
 
        #import bzrlib.status
175
 
        #bzrlib.status.tree_status(Branch('.'))
176
 
        Branch('.').show_status(show_all=all)
 
230
    def run(self, all=False, show_ids=False, file_list=None):
 
231
        if file_list:
 
232
            b = Branch(file_list[0], lock_mode='r')
 
233
            file_list = [b.relpath(x) for x in file_list]
 
234
            # special case: only one path was given and it's the root
 
235
            # of the branch
 
236
            if file_list == ['']:
 
237
                file_list = None
 
238
        else:
 
239
            b = Branch('.', lock_mode='r')
 
240
        import status
 
241
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
 
242
                           specific_files=file_list)
177
243
 
178
244
 
179
245
class cmd_cat_revision(Command):
222
288
        bzrlib.add.smart_add(file_list, verbose)
223
289
 
224
290
 
225
 
def Relpath(Command):
 
291
class cmd_relpath(Command):
226
292
    """Show path of a file relative to root"""
227
 
    takes_args = ('filename')
 
293
    takes_args = ['filename']
228
294
    
229
 
    def run(self):
230
 
        print Branch(self.args['filename']).relpath(filename)
 
295
    def run(self, filename):
 
296
        print Branch(filename).relpath(filename)
231
297
 
232
298
 
233
299
 
304
370
 
305
371
 
306
372
class cmd_info(Command):
307
 
    """Show statistical information for this branch"""
308
 
    def run(self):
 
373
    """Show statistical information about a branch."""
 
374
    takes_args = ['branch?']
 
375
    
 
376
    def run(self, branch=None):
309
377
        import info
310
 
        info.show_info(Branch('.'))        
 
378
 
 
379
        from branch import find_branch
 
380
        b = find_branch(branch)
 
381
        info.show_info(b)
311
382
 
312
383
 
313
384
class cmd_remove(Command):
419
490
    
420
491
    takes_args = ['file*']
421
492
    takes_options = ['revision']
 
493
    aliases = ['di']
422
494
 
423
495
    def run(self, revision=None, file_list=None):
424
496
        from bzrlib.diff import show_diff
425
497
    
426
 
        show_diff(Branch('.'), revision, file_list)
 
498
        show_diff(Branch('.'), revision, specific_files=file_list)
 
499
 
 
500
 
 
501
        
427
502
 
428
503
 
429
504
class cmd_deleted(Command):
448
523
                else:
449
524
                    print path
450
525
 
 
526
 
 
527
class cmd_modified(Command):
 
528
    """List files modified in working tree."""
 
529
    hidden = True
 
530
    def run(self):
 
531
        import statcache
 
532
        b = Branch('.')
 
533
        inv = b.read_working_inventory()
 
534
        sc = statcache.update_cache(b, inv)
 
535
        basis = b.basis_tree()
 
536
        basis_inv = basis.inventory
 
537
        
 
538
        # We used to do this through iter_entries(), but that's slow
 
539
        # when most of the files are unmodified, as is usually the
 
540
        # case.  So instead we iterate by inventory entry, and only
 
541
        # calculate paths as necessary.
 
542
 
 
543
        for file_id in basis_inv:
 
544
            cacheentry = sc.get(file_id)
 
545
            if not cacheentry:                 # deleted
 
546
                continue
 
547
            ie = basis_inv[file_id]
 
548
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
 
549
                path = inv.id2path(file_id)
 
550
                print path
 
551
 
 
552
 
 
553
 
 
554
class cmd_added(Command):
 
555
    """List files added in working tree."""
 
556
    hidden = True
 
557
    def run(self):
 
558
        b = Branch('.')
 
559
        wt = b.working_tree()
 
560
        basis_inv = b.basis_tree().inventory
 
561
        inv = wt.inventory
 
562
        for file_id in inv:
 
563
            if file_id in basis_inv:
 
564
                continue
 
565
            path = inv.id2path(file_id)
 
566
            if not os.access(b.abspath(path), os.F_OK):
 
567
                continue
 
568
            print path
 
569
                
 
570
        
 
571
 
451
572
class cmd_root(Command):
452
573
    """Show the tree root directory.
453
574
 
456
577
    takes_args = ['filename?']
457
578
    def run(self, filename=None):
458
579
        """Print the branch root."""
459
 
        print bzrlib.branch.find_branch_root(filename)
460
 
 
 
580
        from branch import find_branch
 
581
        b = find_branch(filename)
 
582
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
461
583
 
462
584
 
463
585
class cmd_log(Command):
464
586
    """Show log of this branch.
465
587
 
466
 
    TODO: Options to show ids; to limit range; etc.
 
588
    TODO: Option to limit range.
 
589
 
 
590
    TODO: Perhaps show most-recent first with an option for last.
467
591
    """
468
 
    takes_options = ['timezone', 'verbose']
469
 
    def run(self, timezone='original', verbose=False):
470
 
        Branch('.').write_log(show_timezone=timezone, verbose=verbose)
 
592
    takes_args = ['filename?']
 
593
    takes_options = ['timezone', 'verbose', 'show-ids']
 
594
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
 
595
        from branch import find_branch
 
596
        b = find_branch((filename or '.'), lock_mode='r')
 
597
        if filename:
 
598
            filename = b.relpath(filename)
 
599
        bzrlib.show_log(b, filename,
 
600
                        show_timezone=timezone,
 
601
                        verbose=verbose,
 
602
                        show_ids=show_ids)
 
603
 
 
604
 
 
605
 
 
606
class cmd_touching_revisions(Command):
 
607
    """Return revision-ids which affected a particular file."""
 
608
    hidden = True
 
609
    takes_args = ["filename"]
 
610
    def run(self, filename):
 
611
        b = Branch(filename, lock_mode='r')
 
612
        inv = b.read_working_inventory()
 
613
        file_id = inv.path2id(b.relpath(filename))
 
614
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
 
615
            print "%6d %s" % (revno, what)
471
616
 
472
617
 
473
618
class cmd_ls(Command):
507
652
 
508
653
 
509
654
class cmd_ignore(Command):
510
 
    """Ignore a command or pattern"""
 
655
    """Ignore a command or pattern
 
656
 
 
657
    To remove patterns from the ignore list, edit the .bzrignore file.
 
658
 
 
659
    If the pattern contains a slash, it is compared to the whole path
 
660
    from the branch root.  Otherwise, it is comapred to only the last
 
661
    component of the path.
 
662
 
 
663
    Ignore patterns are case-insensitive on case-insensitive systems.
 
664
 
 
665
    Note: wildcards must be quoted from the shell on Unix.
 
666
 
 
667
    examples:
 
668
        bzr ignore ./Makefile
 
669
        bzr ignore '*.class'
 
670
    """
511
671
    takes_args = ['name_pattern']
512
672
    
513
673
    def run(self, name_pattern):
 
674
        from bzrlib.atomicfile import AtomicFile
 
675
        import codecs
 
676
 
514
677
        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()
 
678
        ifn = b.abspath('.bzrignore')
 
679
 
 
680
        # FIXME: probably doesn't handle non-ascii patterns
 
681
 
 
682
        if os.path.exists(ifn):
 
683
            f = b.controlfile(ifn, 'rt')
 
684
            igns = f.read()
 
685
            f.close()
 
686
        else:
 
687
            igns = ''
 
688
 
 
689
        if igns and igns[-1] != '\n':
 
690
            igns += '\n'
 
691
        igns += name_pattern + '\n'
 
692
 
 
693
        f = AtomicFile(ifn, 'wt')
 
694
        f.write(igns)
 
695
        f.commit()
520
696
 
521
697
        inv = b.working_tree().inventory
522
698
        if inv.path2id('.bzrignore'):
528
704
 
529
705
 
530
706
class cmd_ignored(Command):
531
 
    """List ignored files and the patterns that matched them."""
 
707
    """List ignored files and the patterns that matched them.
 
708
 
 
709
    See also: bzr ignore"""
532
710
    def run(self):
533
711
        tree = Branch('.').working_tree()
534
712
        for path, file_class, kind, file_id in tree.list_files():
544
722
 
545
723
    example:
546
724
        bzr lookup-revision 33
547
 
        """
 
725
    """
548
726
    hidden = True
 
727
    takes_args = ['revno']
 
728
    
549
729
    def run(self, revno):
550
730
        try:
551
731
            revno = int(revno)
552
732
        except ValueError:
553
 
            raise BzrError("not a valid revision-number: %r" % revno)
554
 
 
555
 
        print Branch('.').lookup_revision(revno) or NONE_STRING
556
 
 
 
733
            raise BzrCommandError("not a valid revision-number: %r" % revno)
 
734
 
 
735
        print Branch('.').lookup_revision(revno)
557
736
 
558
737
 
559
738
class cmd_export(Command):
562
741
    If no revision is specified this exports the last committed revision."""
563
742
    takes_args = ['dest']
564
743
    takes_options = ['revision']
565
 
    def run(self, dest, revno=None):
 
744
    def run(self, dest, revision=None):
566
745
        b = Branch('.')
567
 
        if revno == None:
568
 
            rh = b.revision_history[-1]
 
746
        if revision == None:
 
747
            rh = b.revision_history()[-1]
569
748
        else:
570
 
            rh = b.lookup_revision(int(revno))
 
749
            rh = b.lookup_revision(int(revision))
571
750
        t = b.revision_tree(rh)
572
751
        t.export(dest)
573
752
 
596
775
class cmd_commit(Command):
597
776
    """Commit changes into a new revision.
598
777
 
599
 
    TODO: Commit only selected files.
 
778
    If selected files are specified, only changes to those files are
 
779
    committed.  If a directory is specified then its contents are also
 
780
    committed.
 
781
 
 
782
    A selected-file commit may fail in some cases where the committed
 
783
    tree would be invalid, such as trying to commit a file in a
 
784
    newly-added directory that is not itself committed.
600
785
 
601
786
    TODO: Run hooks on tree to-be-committed, and after commit.
602
787
 
603
788
    TODO: Strict commit that fails if there are unknown or deleted files.
604
789
    """
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)
 
790
    takes_args = ['selected*']
 
791
    takes_options = ['message', 'file', 'verbose']
 
792
    aliases = ['ci', 'checkin']
 
793
 
 
794
    def run(self, message=None, file=None, verbose=False, selected_list=None):
 
795
        from bzrlib.commit import commit
 
796
 
 
797
        ## Warning: shadows builtin file()
 
798
        if not message and not file:
 
799
            raise BzrCommandError("please specify a commit message",
 
800
                                  ["use either --message or --file"])
 
801
        elif message and file:
 
802
            raise BzrCommandError("please specify either --message or --file")
 
803
        
 
804
        if file:
 
805
            import codecs
 
806
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
 
807
 
 
808
        b = Branch('.')
 
809
        commit(b, message, verbose=verbose, specific_files=selected_list)
611
810
 
612
811
 
613
812
class cmd_check(Command):
683
882
    def run(self):
684
883
        print "it sure does!"
685
884
 
 
885
def parse_spec(spec):
 
886
    if '/@' in spec:
 
887
        parsed = spec.split('/@')
 
888
        assert len(parsed) == 2
 
889
        if parsed[1] == "":
 
890
            parsed[1] = -1
 
891
        else:
 
892
            parsed[1] = int(parsed[1])
 
893
            assert parsed[1] >=0
 
894
    else:
 
895
        parsed = [spec, None]
 
896
    return parsed
 
897
 
 
898
class cmd_merge(Command):
 
899
    """Perform a three-way merge of trees."""
 
900
    takes_args = ['other_spec', 'base_spec']
 
901
 
 
902
    def run(self, other_spec, base_spec):
 
903
        merge.merge(parse_spec(other_spec), parse_spec(base_spec))
686
904
 
687
905
class cmd_assert_fail(Command):
688
906
    """Test reporting of assertion failures"""
696
914
 
697
915
    For a list of all available commands, say 'bzr help commands'."""
698
916
    takes_args = ['topic?']
 
917
    aliases = ['?']
699
918
    
700
919
    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
 
            
 
920
        import help
 
921
        help.help(topic)
 
922
 
 
923
 
 
924
class cmd_update_stat_cache(Command):
 
925
    """Update stat-cache mapping inodes to SHA-1 hashes.
 
926
 
 
927
    For testing only."""
 
928
    hidden = True
 
929
    def run(self):
 
930
        import statcache
 
931
        b = Branch('.')
 
932
        statcache.update_cache(b.base, b.read_working_inventory())
 
933
 
779
934
 
780
935
######################################################################
781
936
# main routine
787
942
OPTIONS = {
788
943
    'all':                    None,
789
944
    'help':                   None,
 
945
    'file':                   unicode,
790
946
    'message':                unicode,
791
947
    'profile':                None,
792
948
    'revision':               int,
799
955
 
800
956
SHORT_OPTIONS = {
801
957
    'm':                      'message',
 
958
    'F':                      'file', 
802
959
    'r':                      'revision',
803
960
    'v':                      'verbose',
804
961
}
921
1078
    This is similar to main(), but without all the trappings for
922
1079
    logging and error handling.  
923
1080
    """
924
 
 
925
1081
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
926
1082
    
927
1083
    try:
928
1084
        args, opts = parse_args(argv[1:])
929
1085
        if 'help' in opts:
 
1086
            import help
930
1087
            if args:
931
 
                help(args[0])
 
1088
                help.help(args[0])
932
1089
            else:
933
 
                help()
 
1090
                help.help()
934
1091
            return 0
935
1092
        elif 'version' in opts:
936
 
            cmd_version([], [])
 
1093
            show_version()
937
1094
            return 0
938
1095
        cmd = str(args.pop(0))
939
1096
    except IndexError:
940
 
        log_error('usage: bzr COMMAND')
941
 
        log_error('  try "bzr help"')
 
1097
        import help
 
1098
        help.help()
942
1099
        return 1
 
1100
          
943
1101
 
944
1102
    canonical_cmd, cmd_class = get_cmd_class(cmd)
945
1103
 
954
1112
    allowed = cmd_class.takes_options
955
1113
    for oname in opts:
956
1114
        if oname not in allowed:
957
 
            raise BzrCommandError("option %r is not allowed for command %r"
 
1115
            raise BzrCommandError("option '--%s' is not allowed for command %r"
958
1116
                                  % (oname, cmd))
959
1117
 
960
1118
    # mix arguments and options into one dictionary
964
1122
        cmdopts[k.replace('-', '_')] = v
965
1123
 
966
1124
    if profile:
967
 
        import hotshot
 
1125
        import hotshot, tempfile
968
1126
        pffileno, pfname = tempfile.mkstemp()
969
1127
        try:
970
1128
            prof = hotshot.Profile(pfname)
979
1137
            ## print_stats seems hardcoded to stdout
980
1138
            stats.print_stats(20)
981
1139
            
982
 
            return ret
 
1140
            return ret.status
983
1141
 
984
1142
        finally:
985
1143
            os.close(pffileno)
986
1144
            os.remove(pfname)
987
1145
    else:
988
 
        cmdobj = cmd_class(cmdopts, cmdargs) or 0
989
 
 
990
 
 
991
 
 
992
 
def _report_exception(e, summary, quiet=False):
 
1146
        cmdobj = cmd_class(cmdopts, cmdargs).status 
 
1147
 
 
1148
 
 
1149
def _report_exception(summary, quiet=False):
993
1150
    import traceback
994
1151
    log_error('bzr: ' + summary)
995
 
    bzrlib.trace.log_exception(e)
 
1152
    bzrlib.trace.log_exception()
996
1153
 
997
1154
    if not quiet:
998
1155
        tb = sys.exc_info()[2]
1006
1163
def main(argv):
1007
1164
    import errno
1008
1165
    
1009
 
    bzrlib.trace.create_tracefile(argv)
 
1166
    bzrlib.open_tracefile(argv)
1010
1167
 
1011
1168
    try:
1012
1169
        try:
1013
 
            ret = run_bzr(argv)
1014
 
            # do this here to catch EPIPE
1015
 
            sys.stdout.flush()
1016
 
            return ret
 
1170
            try:
 
1171
                return run_bzr(argv)
 
1172
            finally:
 
1173
                # do this here inside the exception wrappers to catch EPIPE
 
1174
                sys.stdout.flush()
1017
1175
        except BzrError, e:
1018
1176
            quiet = isinstance(e, (BzrCommandError))
1019
 
            _report_exception(e, 'error: ' + e.args[0], quiet=quiet)
 
1177
            _report_exception('error: ' + e.args[0], quiet=quiet)
1020
1178
            if len(e.args) > 1:
1021
1179
                for h in e.args[1]:
1022
1180
                    # some explanation or hints
1026
1184
            msg = 'assertion failed'
1027
1185
            if str(e):
1028
1186
                msg += ': ' + str(e)
1029
 
            _report_exception(e, msg)
 
1187
            _report_exception(msg)
1030
1188
            return 2
1031
1189
        except KeyboardInterrupt, e:
1032
 
            _report_exception(e, 'interrupted', quiet=True)
 
1190
            _report_exception('interrupted', quiet=True)
1033
1191
            return 2
1034
1192
        except Exception, e:
1035
1193
            quiet = False
1036
 
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
 
1194
            if (isinstance(e, IOError) 
 
1195
                and hasattr(e, 'errno')
 
1196
                and e.errno == errno.EPIPE):
1037
1197
                quiet = True
1038
1198
                msg = 'broken pipe'
1039
1199
            else:
1040
1200
                msg = str(e).rstrip('\n')
1041
 
            _report_exception(e, msg, quiet)
 
1201
            _report_exception(msg, quiet)
1042
1202
            return 2
1043
1203
    finally:
1044
1204
        bzrlib.trace.close_trace()