~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-13 00:57:32 UTC
  • Revision ID: mbp@sourcefrog.net-20050513005732-26b0a3042cbb57d1
- more notes on tagging

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
30
 
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
 
 
 
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: Perhaps show most-recent first with an option for last.
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 branch import find_branch
 
595
        b = find_branch((filename or '.'), lock_mode='r')
 
596
        if filename:
 
597
            filename = b.relpath(filename)
 
598
        bzrlib.show_log(b, filename,
 
599
                        show_timezone=timezone,
 
600
                        verbose=verbose,
 
601
                        show_ids=show_ids)
 
602
 
 
603
 
 
604
 
 
605
class cmd_touching_revisions(Command):
 
606
    """Return revision-ids which affected a particular file."""
 
607
    hidden = True
 
608
    takes_args = ["filename"]
 
609
    def run(self, filename):
 
610
        b = Branch(filename, lock_mode='r')
 
611
        inv = b.read_working_inventory()
 
612
        file_id = inv.path2id(b.relpath(filename))
 
613
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
 
614
            print "%6d %s" % (revno, what)
471
615
 
472
616
 
473
617
class cmd_ls(Command):
507
651
 
508
652
 
509
653
class cmd_ignore(Command):
510
 
    """Ignore a command or pattern"""
 
654
    """Ignore a command or pattern
 
655
 
 
656
    To remove patterns from the ignore list, edit the .bzrignore file.
 
657
 
 
658
    If the pattern contains a slash, it is compared to the whole path
 
659
    from the branch root.  Otherwise, it is comapred to only the last
 
660
    component of the path.
 
661
 
 
662
    Ignore patterns are case-insensitive on case-insensitive systems.
 
663
 
 
664
    Note: wildcards must be quoted from the shell on Unix.
 
665
 
 
666
    examples:
 
667
        bzr ignore ./Makefile
 
668
        bzr ignore '*.class'
 
669
    """
511
670
    takes_args = ['name_pattern']
512
671
    
513
672
    def run(self, name_pattern):
 
673
        from bzrlib.atomicfile import AtomicFile
 
674
        import codecs
 
675
 
514
676
        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()
 
677
        ifn = b.abspath('.bzrignore')
 
678
 
 
679
        # FIXME: probably doesn't handle non-ascii patterns
 
680
 
 
681
        if os.path.exists(ifn):
 
682
            f = b.controlfile(ifn, 'rt')
 
683
            igns = f.read()
 
684
            f.close()
 
685
        else:
 
686
            igns = ''
 
687
 
 
688
        if igns and igns[-1] != '\n':
 
689
            igns += '\n'
 
690
        igns += name_pattern + '\n'
 
691
 
 
692
        f = AtomicFile(ifn, 'wt')
 
693
        f.write(igns)
 
694
        f.commit()
520
695
 
521
696
        inv = b.working_tree().inventory
522
697
        if inv.path2id('.bzrignore'):
528
703
 
529
704
 
530
705
class cmd_ignored(Command):
531
 
    """List ignored files and the patterns that matched them."""
 
706
    """List ignored files and the patterns that matched them.
 
707
 
 
708
    See also: bzr ignore"""
532
709
    def run(self):
533
710
        tree = Branch('.').working_tree()
534
711
        for path, file_class, kind, file_id in tree.list_files():
544
721
 
545
722
    example:
546
723
        bzr lookup-revision 33
547
 
        """
 
724
    """
548
725
    hidden = True
 
726
    takes_args = ['revno']
 
727
    
549
728
    def run(self, revno):
550
729
        try:
551
730
            revno = int(revno)
552
731
        except ValueError:
553
 
            raise BzrError("not a valid revision-number: %r" % revno)
554
 
 
555
 
        print Branch('.').lookup_revision(revno) or NONE_STRING
556
 
 
 
732
            raise BzrCommandError("not a valid revision-number: %r" % revno)
 
733
 
 
734
        print Branch('.').lookup_revision(revno)
557
735
 
558
736
 
559
737
class cmd_export(Command):
562
740
    If no revision is specified this exports the last committed revision."""
563
741
    takes_args = ['dest']
564
742
    takes_options = ['revision']
565
 
    def run(self, dest, revno=None):
 
743
    def run(self, dest, revision=None):
566
744
        b = Branch('.')
567
 
        if revno == None:
568
 
            rh = b.revision_history[-1]
 
745
        if revision == None:
 
746
            rh = b.revision_history()[-1]
569
747
        else:
570
 
            rh = b.lookup_revision(int(revno))
 
748
            rh = b.lookup_revision(int(revision))
571
749
        t = b.revision_tree(rh)
572
750
        t.export(dest)
573
751
 
596
774
class cmd_commit(Command):
597
775
    """Commit changes into a new revision.
598
776
 
599
 
    TODO: Commit only selected files.
 
777
    If selected files are specified, only changes to those files are
 
778
    committed.  If a directory is specified then its contents are also
 
779
    committed.
 
780
 
 
781
    A selected-file commit may fail in some cases where the committed
 
782
    tree would be invalid, such as trying to commit a file in a
 
783
    newly-added directory that is not itself committed.
600
784
 
601
785
    TODO: Run hooks on tree to-be-committed, and after commit.
602
786
 
603
787
    TODO: Strict commit that fails if there are unknown or deleted files.
604
788
    """
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)
 
789
    takes_args = ['selected*']
 
790
    takes_options = ['message', 'file', 'verbose']
 
791
    aliases = ['ci', 'checkin']
 
792
 
 
793
    def run(self, message=None, file=None, verbose=False, selected_list=None):
 
794
        from bzrlib.commit import commit
 
795
 
 
796
        ## Warning: shadows builtin file()
 
797
        if not message and not file:
 
798
            raise BzrCommandError("please specify a commit message",
 
799
                                  ["use either --message or --file"])
 
800
        elif message and file:
 
801
            raise BzrCommandError("please specify either --message or --file")
 
802
        
 
803
        if file:
 
804
            import codecs
 
805
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
 
806
 
 
807
        b = Branch('.')
 
808
        commit(b, message, verbose=verbose, specific_files=selected_list)
611
809
 
612
810
 
613
811
class cmd_check(Command):
696
894
 
697
895
    For a list of all available commands, say 'bzr help commands'."""
698
896
    takes_args = ['topic?']
 
897
    aliases = ['?']
699
898
    
700
899
    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
 
            
 
900
        import help
 
901
        help.help(topic)
 
902
 
 
903
 
 
904
class cmd_update_stat_cache(Command):
 
905
    """Update stat-cache mapping inodes to SHA-1 hashes.
 
906
 
 
907
    For testing only."""
 
908
    hidden = True
 
909
    def run(self):
 
910
        import statcache
 
911
        b = Branch('.')
 
912
        statcache.update_cache(b.base, b.read_working_inventory())
 
913
 
779
914
 
780
915
######################################################################
781
916
# main routine
787
922
OPTIONS = {
788
923
    'all':                    None,
789
924
    'help':                   None,
 
925
    'file':                   unicode,
790
926
    'message':                unicode,
791
927
    'profile':                None,
792
928
    'revision':               int,
799
935
 
800
936
SHORT_OPTIONS = {
801
937
    'm':                      'message',
 
938
    'F':                      'file', 
802
939
    'r':                      'revision',
803
940
    'v':                      'verbose',
804
941
}
921
1058
    This is similar to main(), but without all the trappings for
922
1059
    logging and error handling.  
923
1060
    """
924
 
 
925
1061
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
926
1062
    
927
1063
    try:
928
1064
        args, opts = parse_args(argv[1:])
929
1065
        if 'help' in opts:
 
1066
            import help
930
1067
            if args:
931
 
                help(args[0])
 
1068
                help.help(args[0])
932
1069
            else:
933
 
                help()
 
1070
                help.help()
934
1071
            return 0
935
1072
        elif 'version' in opts:
936
 
            cmd_version([], [])
 
1073
            show_version()
937
1074
            return 0
938
1075
        cmd = str(args.pop(0))
939
1076
    except IndexError:
940
 
        log_error('usage: bzr COMMAND')
941
 
        log_error('  try "bzr help"')
 
1077
        import help
 
1078
        help.help()
942
1079
        return 1
 
1080
          
943
1081
 
944
1082
    canonical_cmd, cmd_class = get_cmd_class(cmd)
945
1083
 
954
1092
    allowed = cmd_class.takes_options
955
1093
    for oname in opts:
956
1094
        if oname not in allowed:
957
 
            raise BzrCommandError("option %r is not allowed for command %r"
 
1095
            raise BzrCommandError("option '--%s' is not allowed for command %r"
958
1096
                                  % (oname, cmd))
959
1097
 
960
1098
    # mix arguments and options into one dictionary
964
1102
        cmdopts[k.replace('-', '_')] = v
965
1103
 
966
1104
    if profile:
967
 
        import hotshot
 
1105
        import hotshot, tempfile
968
1106
        pffileno, pfname = tempfile.mkstemp()
969
1107
        try:
970
1108
            prof = hotshot.Profile(pfname)
979
1117
            ## print_stats seems hardcoded to stdout
980
1118
            stats.print_stats(20)
981
1119
            
982
 
            return ret
 
1120
            return ret.status
983
1121
 
984
1122
        finally:
985
1123
            os.close(pffileno)
986
1124
            os.remove(pfname)
987
1125
    else:
988
 
        cmdobj = cmd_class(cmdopts, cmdargs) or 0
989
 
 
990
 
 
991
 
 
992
 
def _report_exception(e, summary, quiet=False):
 
1126
        cmdobj = cmd_class(cmdopts, cmdargs).status 
 
1127
 
 
1128
 
 
1129
def _report_exception(summary, quiet=False):
993
1130
    import traceback
994
1131
    log_error('bzr: ' + summary)
995
 
    bzrlib.trace.log_exception(e)
 
1132
    bzrlib.trace.log_exception()
996
1133
 
997
1134
    if not quiet:
998
1135
        tb = sys.exc_info()[2]
1006
1143
def main(argv):
1007
1144
    import errno
1008
1145
    
1009
 
    bzrlib.trace.create_tracefile(argv)
 
1146
    bzrlib.open_tracefile(argv)
1010
1147
 
1011
1148
    try:
1012
1149
        try:
1013
 
            ret = run_bzr(argv)
1014
 
            # do this here to catch EPIPE
1015
 
            sys.stdout.flush()
1016
 
            return ret
 
1150
            try:
 
1151
                return run_bzr(argv)
 
1152
            finally:
 
1153
                # do this here inside the exception wrappers to catch EPIPE
 
1154
                sys.stdout.flush()
1017
1155
        except BzrError, e:
1018
1156
            quiet = isinstance(e, (BzrCommandError))
1019
 
            _report_exception(e, 'error: ' + e.args[0], quiet=quiet)
 
1157
            _report_exception('error: ' + e.args[0], quiet=quiet)
1020
1158
            if len(e.args) > 1:
1021
1159
                for h in e.args[1]:
1022
1160
                    # some explanation or hints
1026
1164
            msg = 'assertion failed'
1027
1165
            if str(e):
1028
1166
                msg += ': ' + str(e)
1029
 
            _report_exception(e, msg)
 
1167
            _report_exception(msg)
1030
1168
            return 2
1031
1169
        except KeyboardInterrupt, e:
1032
 
            _report_exception(e, 'interrupted', quiet=True)
 
1170
            _report_exception('interrupted', quiet=True)
1033
1171
            return 2
1034
1172
        except Exception, e:
1035
1173
            quiet = False
1036
 
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
 
1174
            if (isinstance(e, IOError) 
 
1175
                and hasattr(e, 'errno')
 
1176
                and e.errno == errno.EPIPE):
1037
1177
                quiet = True
1038
1178
                msg = 'broken pipe'
1039
1179
            else:
1040
1180
                msg = str(e).rstrip('\n')
1041
 
            _report_exception(e, msg, quiet)
 
1181
            _report_exception(msg, quiet)
1042
1182
            return 2
1043
1183
    finally:
1044
1184
        bzrlib.trace.close_trace()