~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-03 07:48:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050503074854-adb6f9d6382e27a9
- sketchy experiments in bash and zsh completion

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
 
 
18
 
# TODO: probably should say which arguments are candidates for glob
19
 
# expansion on windows and do that at the command level.
20
 
 
21
 
# TODO: Help messages for options.
22
 
 
23
 
# TODO: Define arguments by objects, rather than just using names.
24
 
# Those objects can specify the expected type of the argument, which
25
 
# would help with validation and shell completion.
26
 
 
27
 
# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
28
 
# the profile output behind so it can be interactively examined?
29
 
 
30
 
import sys
31
 
import os
32
 
from warnings import warn
33
 
from inspect import getdoc
 
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
34
72
 
35
73
import bzrlib
36
 
import bzrlib.trace
37
 
from bzrlib.trace import mutter, note, log_error, warning
38
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
39
 
from bzrlib.revisionspec import RevisionSpec
40
 
from bzrlib import BZRDIR
41
 
 
42
 
plugin_cmds = {}
43
 
 
44
 
 
45
 
def register_command(cmd):
46
 
    "Utility function to help register a command"
47
 
    global plugin_cmds
48
 
    k = cmd.__name__
49
 
    if k.startswith("cmd_"):
50
 
        k_unsquished = _unsquish_command_name(k)
51
 
    else:
52
 
        k_unsquished = k
53
 
    if not plugin_cmds.has_key(k_unsquished):
54
 
        plugin_cmds[k_unsquished] = cmd
55
 
        mutter('registered plugin command %s', k_unsquished)      
56
 
    else:
57
 
        log_error('Two plugins defined the same command: %r' % k)
58
 
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
59
 
 
60
 
 
61
 
def _squish_command_name(cmd):
62
 
    return 'cmd_' + cmd.replace('-', '_')
63
 
 
64
 
 
65
 
def _unsquish_command_name(cmd):
66
 
    assert cmd.startswith("cmd_")
67
 
    return cmd[4:].replace('_','-')
68
 
 
69
 
 
70
 
def _parse_revision_str(revstr):
71
 
    """This handles a revision string -> revno.
72
 
 
73
 
    This always returns a list.  The list will have one element for
74
 
    each revision specifier supplied.
75
 
 
76
 
    >>> _parse_revision_str('234')
77
 
    [<RevisionSpec_int 234>]
78
 
    >>> _parse_revision_str('234..567')
79
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 567>]
80
 
    >>> _parse_revision_str('..')
81
 
    [<RevisionSpec None>, <RevisionSpec None>]
82
 
    >>> _parse_revision_str('..234')
83
 
    [<RevisionSpec None>, <RevisionSpec_int 234>]
84
 
    >>> _parse_revision_str('234..')
85
 
    [<RevisionSpec_int 234>, <RevisionSpec None>]
86
 
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
87
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
88
 
    >>> _parse_revision_str('234....789') #Error ?
89
 
    [<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
90
 
    >>> _parse_revision_str('revid:test@other.com-234234')
91
 
    [<RevisionSpec_revid revid:test@other.com-234234>]
92
 
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
93
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
94
 
    >>> _parse_revision_str('revid:test@other.com-234234..23')
95
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
96
 
    >>> _parse_revision_str('date:2005-04-12')
97
 
    [<RevisionSpec_date date:2005-04-12>]
98
 
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
99
 
    [<RevisionSpec_date date:2005-04-12 12:24:33>]
100
 
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
101
 
    [<RevisionSpec_date date:2005-04-12T12:24:33>]
102
 
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
103
 
    [<RevisionSpec_date date:2005-04-12,12:24:33>]
104
 
    >>> _parse_revision_str('-5..23')
105
 
    [<RevisionSpec_int -5>, <RevisionSpec_int 23>]
106
 
    >>> _parse_revision_str('-5')
107
 
    [<RevisionSpec_int -5>]
108
 
    >>> _parse_revision_str('123a')
109
 
    Traceback (most recent call last):
110
 
      ...
111
 
    BzrError: No namespace registered for string: '123a'
112
 
    >>> _parse_revision_str('abc')
113
 
    Traceback (most recent call last):
114
 
      ...
115
 
    BzrError: No namespace registered for string: 'abc'
116
 
    >>> _parse_revision_str('branch:../branch2')
117
 
    [<RevisionSpec_branch branch:../branch2>]
118
 
    """
119
 
    import re
120
 
    old_format_re = re.compile('\d*:\d*')
121
 
    m = old_format_re.match(revstr)
122
 
    revs = []
123
 
    if m:
124
 
        warning('Colon separator for revision numbers is deprecated.'
125
 
                ' Use .. instead')
126
 
        for rev in revstr.split(':'):
127
 
            if rev:
128
 
                revs.append(RevisionSpec(int(rev)))
129
 
            else:
130
 
                revs.append(RevisionSpec(None))
131
 
    else:
132
 
        next_prefix = None
133
 
        for x in revstr.split('..'):
134
 
            if not x:
135
 
                revs.append(RevisionSpec(None))
136
 
            elif x[-1] == ':':
137
 
                # looks like a namespace:.. has happened
138
 
                next_prefix = x + '..'
139
 
            else:
140
 
                if next_prefix is not None:
141
 
                    x = next_prefix + x
142
 
                revs.append(RevisionSpec(x))
143
 
                next_prefix = None
144
 
        if next_prefix is not None:
145
 
            revs.append(RevisionSpec(next_prefix))
146
 
    return revs
147
 
 
148
 
 
149
 
def _builtin_commands():
150
 
    import bzrlib.builtins
151
 
    r = {}
152
 
    builtins = bzrlib.builtins.__dict__
153
 
    for name in builtins:
154
 
        if name.startswith("cmd_"):
155
 
            real_name = _unsquish_command_name(name)        
156
 
            r[real_name] = builtins[name]
157
 
    return r
158
 
 
159
 
            
160
 
 
161
 
def builtin_command_names():
162
 
    """Return list of builtin command names."""
163
 
    return _builtin_commands().keys()
164
 
    
165
 
 
166
 
def plugin_command_names():
167
 
    return plugin_cmds.keys()
168
 
 
169
 
 
170
 
def _get_cmd_dict(plugins_override=True):
171
 
    """Return name->class mapping for all commands."""
172
 
    d = _builtin_commands()
173
 
    if plugins_override:
174
 
        d.update(plugin_cmds)
175
 
    return d
176
 
 
177
 
    
178
 
def get_all_cmds(plugins_override=True):
179
 
    """Return canonical name and class for all registered commands."""
180
 
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
181
 
        yield k,v
182
 
 
183
 
 
184
 
def get_cmd_object(cmd_name, plugins_override=True):
185
 
    """Return the canonical name and command class for a command.
186
 
 
187
 
    plugins_override
188
 
        If true, plugin commands can override builtins.
189
 
    """
190
 
    from bzrlib.externalcommand import ExternalCommand
191
 
 
192
 
    cmd_name = str(cmd_name)            # not unicode
193
 
 
194
 
    # first look up this command under the specified name
195
 
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
74
from bzrlib.store import ImmutableStore
 
75
from bzrlib.trace import mutter, note, log_error
 
76
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
77
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
78
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
79
from bzrlib.revision import Revision
 
80
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
81
     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
 
 
100
 
 
101
def get_cmd_class(cmd):
 
102
    cmd = str(cmd)
 
103
    
 
104
    cmd = CMD_ALIASES.get(cmd, cmd)
 
105
    
196
106
    try:
197
 
        return cmds[cmd_name]()
 
107
        cmd_class = globals()['cmd_' + cmd.replace('-', '_')]
198
108
    except KeyError:
199
 
        pass
200
 
 
201
 
    # look for any command which claims this as an alias
202
 
    for real_cmd_name, cmd_class in cmds.iteritems():
203
 
        if cmd_name in cmd_class.aliases:
204
 
            return cmd_class()
205
 
 
206
 
    cmd_obj = ExternalCommand.find_command(cmd_name)
207
 
    if cmd_obj:
208
 
        return cmd_obj
209
 
 
210
 
    raise BzrCommandError("unknown command %r" % cmd_name)
211
 
 
212
 
 
213
 
class Command(object):
 
109
        raise BzrError("unknown command %r" % cmd)
 
110
 
 
111
    return cmd, cmd_class
 
112
 
 
113
 
 
114
 
 
115
class Command:
214
116
    """Base class for commands.
215
117
 
216
 
    Commands are the heart of the command-line bzr interface.
217
 
 
218
 
    The command object mostly handles the mapping of command-line
219
 
    parameters into one or more bzrlib operations, and of the results
220
 
    into textual output.
221
 
 
222
 
    Commands normally don't have any state.  All their arguments are
223
 
    passed in to the run method.  (Subclasses may take a different
224
 
    policy if the behaviour of the instance needs to depend on e.g. a
225
 
    shell plugin and not just its Python class.)
226
 
 
227
118
    The docstring for an actual command should give a single-line
228
119
    summary, then a complete description of the command.  A grammar
229
120
    description will be inserted.
230
121
 
231
 
    aliases
232
 
        Other accepted names for this command.
233
 
 
234
122
    takes_args
235
123
        List of argument forms, marked with whether they are optional,
236
124
        repeated, etc.
239
127
        List of options that may be given for this command.
240
128
 
241
129
    hidden
242
 
        If true, this command isn't advertised.  This is typically
243
 
        for commands intended for expert users.
 
130
        If true, this command isn't advertised.
244
131
    """
245
132
    aliases = []
246
133
    
249
136
 
250
137
    hidden = False
251
138
    
252
 
    def __init__(self):
253
 
        """Construct an instance of this command."""
254
 
        if self.__doc__ == Command.__doc__:
255
 
            warn("No help message set for %r" % self)
256
 
 
257
 
 
258
 
    def run_argv(self, argv):
259
 
        """Parse command line and run."""
260
 
        args, opts = parse_args(argv)
261
 
 
262
 
        if 'help' in opts:  # e.g. bzr add --help
263
 
            from bzrlib.help import help_on_command
264
 
            help_on_command(self.name())
265
 
            return 0
266
 
 
267
 
        # check options are reasonable
268
 
        allowed = self.takes_options
269
 
        for oname in opts:
270
 
            if oname not in allowed:
271
 
                raise BzrCommandError("option '--%s' is not allowed for command %r"
272
 
                                      % (oname, self.name()))
273
 
 
274
 
        # mix arguments and options into one dictionary
275
 
        cmdargs = _match_argform(self.name(), self.takes_args, args)
276
 
        cmdopts = {}
277
 
        for k, v in opts.items():
278
 
            cmdopts[k.replace('-', '_')] = v
279
 
 
280
 
        all_cmd_args = cmdargs.copy()
281
 
        all_cmd_args.update(cmdopts)
282
 
 
283
 
        return self.run(**all_cmd_args)
 
139
    def __init__(self, options, arguments):
 
140
        """Construct and run the command.
 
141
 
 
142
        Sets self.status to the return value of run()."""
 
143
        assert isinstance(options, dict)
 
144
        assert isinstance(arguments, dict)
 
145
        cmdargs = options.copy()
 
146
        cmdargs.update(arguments)
 
147
        assert self.__doc__ != Command.__doc__, \
 
148
               ("No help message set for %r" % self)
 
149
        self.status = self.run(**cmdargs)
284
150
 
285
151
    
286
152
    def run(self):
287
 
        """Actually run the command.
 
153
        """Override this in sub-classes.
288
154
 
289
155
        This is invoked with the options and arguments bound to
290
156
        keyword parameters.
291
157
 
292
 
        Return 0 or None if the command was successful, or a non-zero
293
 
        shell error code if not.  It's OK for this method to allow
294
 
        an exception to raise up.
295
 
        """
296
 
        raise NotImplementedError()
297
 
 
298
 
 
299
 
    def help(self):
300
 
        """Return help message for this class."""
301
 
        if self.__doc__ is Command.__doc__:
302
 
            return None
303
 
        return getdoc(self)
304
 
 
305
 
    def name(self):
306
 
        return _unsquish_command_name(self.__class__.__name__)
307
 
 
308
 
 
309
 
def parse_spec(spec):
310
 
    """
311
 
    >>> parse_spec(None)
312
 
    [None, None]
313
 
    >>> parse_spec("./")
314
 
    ['./', None]
315
 
    >>> parse_spec("../@")
316
 
    ['..', -1]
317
 
    >>> parse_spec("../f/@35")
318
 
    ['../f', 35]
319
 
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
320
 
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
321
 
    """
322
 
    if spec is None:
323
 
        return [None, None]
324
 
    if '/@' in spec:
325
 
        parsed = spec.split('/@')
326
 
        assert len(parsed) == 2
327
 
        if parsed[1] == "":
328
 
            parsed[1] = -1
329
 
        else:
330
 
            try:
331
 
                parsed[1] = int(parsed[1])
332
 
            except ValueError:
333
 
                pass # We can allow stuff like ./@revid:blahblahblah
334
 
            else:
335
 
                assert parsed[1] >=0
336
 
    else:
337
 
        parsed = [spec, None]
338
 
    return parsed
 
158
        Return True if the command was successful, False if not.
 
159
        """
 
160
        return True
 
161
 
 
162
 
 
163
 
 
164
class cmd_status(Command):
 
165
    """Display status summary.
 
166
 
 
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.
 
170
    """
 
171
    takes_options = ['all']
 
172
    
 
173
    def run(self, all=False):
 
174
        #import bzrlib.status
 
175
        #bzrlib.status.tree_status(Branch('.'))
 
176
        Branch('.').show_status(show_all=all)
 
177
 
 
178
 
 
179
class cmd_cat_revision(Command):
 
180
    """Write out metadata for a revision."""
 
181
 
 
182
    hidden = True
 
183
    takes_args = ['revision_id']
 
184
    
 
185
    def run(self, revision_id):
 
186
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
187
 
 
188
 
 
189
class cmd_revno(Command):
 
190
    """Show current revision number.
 
191
 
 
192
    This is equal to the number of revisions on this branch."""
 
193
    def run(self):
 
194
        print Branch('.').revno()
 
195
 
 
196
    
 
197
class cmd_add(Command):
 
198
    """Add specified files or directories.
 
199
 
 
200
    In non-recursive mode, all the named items are added, regardless
 
201
    of whether they were previously ignored.  A warning is given if
 
202
    any of the named files are already versioned.
 
203
 
 
204
    In recursive mode (the default), files are treated the same way
 
205
    but the behaviour for directories is different.  Directories that
 
206
    are already versioned do not give a warning.  All directories,
 
207
    whether already versioned or not, are searched for files or
 
208
    subdirectories that are neither versioned or ignored, and these
 
209
    are added.  This search proceeds recursively into versioned
 
210
    directories.
 
211
 
 
212
    Therefore simply saying 'bzr add .' will version all files that
 
213
    are currently unknown.
 
214
 
 
215
    TODO: Perhaps adding a file whose directly is not versioned should
 
216
    recursively add that parent, rather than giving an error?
 
217
    """
 
218
    takes_args = ['file+']
 
219
    takes_options = ['verbose']
 
220
    
 
221
    def run(self, file_list, verbose=False):
 
222
        bzrlib.add.smart_add(file_list, verbose)
 
223
 
 
224
 
 
225
def Relpath(Command):
 
226
    """Show path of a file relative to root"""
 
227
    takes_args = ('filename')
 
228
    
 
229
    def run(self):
 
230
        print Branch(self.args['filename']).relpath(filename)
 
231
 
 
232
 
 
233
 
 
234
class cmd_inventory(Command):
 
235
    """Show inventory of the current working copy or a revision."""
 
236
    takes_options = ['revision']
 
237
    
 
238
    def run(self, revision=None):
 
239
        b = Branch('.')
 
240
        if revision == None:
 
241
            inv = b.read_working_inventory()
 
242
        else:
 
243
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
244
 
 
245
        for path, entry in inv.iter_entries():
 
246
            print '%-50s %s' % (entry.file_id, path)
 
247
 
 
248
 
 
249
class cmd_move(Command):
 
250
    """Move files to a different directory.
 
251
 
 
252
    examples:
 
253
        bzr move *.txt doc
 
254
 
 
255
    The destination must be a versioned directory in the same branch.
 
256
    """
 
257
    takes_args = ['source$', 'dest']
 
258
    def run(self, source_list, dest):
 
259
        b = Branch('.')
 
260
 
 
261
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
262
 
 
263
 
 
264
class cmd_rename(Command):
 
265
    """Change the name of an entry.
 
266
 
 
267
    examples:
 
268
      bzr rename frob.c frobber.c
 
269
      bzr rename src/frob.c lib/frob.c
 
270
 
 
271
    It is an error if the destination name exists.
 
272
 
 
273
    See also the 'move' command, which moves files into a different
 
274
    directory without changing their name.
 
275
 
 
276
    TODO: Some way to rename multiple files without invoking bzr for each
 
277
    one?"""
 
278
    takes_args = ['from_name', 'to_name']
 
279
    
 
280
    def run(self, from_name, to_name):
 
281
        b = Branch('.')
 
282
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
 
283
 
 
284
 
 
285
 
 
286
class cmd_renames(Command):
 
287
    """Show list of renamed files.
 
288
 
 
289
    TODO: Option to show renames between two historical versions.
 
290
 
 
291
    TODO: Only show renames under dir, rather than in the whole branch.
 
292
    """
 
293
    takes_args = ['dir?']
 
294
 
 
295
    def run(self, dir='.'):
 
296
        b = Branch(dir)
 
297
        old_inv = b.basis_tree().inventory
 
298
        new_inv = b.read_working_inventory()
 
299
 
 
300
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
 
301
        renames.sort()
 
302
        for old_name, new_name in renames:
 
303
            print "%s => %s" % (old_name, new_name)        
 
304
 
 
305
 
 
306
class cmd_info(Command):
 
307
    """Show statistical information for this branch"""
 
308
    def run(self):
 
309
        import info
 
310
        info.show_info(Branch('.'))        
 
311
 
 
312
 
 
313
class cmd_remove(Command):
 
314
    """Make a file unversioned.
 
315
 
 
316
    This makes bzr stop tracking changes to a versioned file.  It does
 
317
    not delete the working copy.
 
318
    """
 
319
    takes_args = ['file+']
 
320
    takes_options = ['verbose']
 
321
    
 
322
    def run(self, file_list, verbose=False):
 
323
        b = Branch(file_list[0])
 
324
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
325
 
 
326
 
 
327
class cmd_file_id(Command):
 
328
    """Print file_id of a particular file or directory.
 
329
 
 
330
    The file_id is assigned when the file is first added and remains the
 
331
    same through all revisions where the file exists, even when it is
 
332
    moved or renamed.
 
333
    """
 
334
    hidden = True
 
335
    takes_args = ['filename']
 
336
    def run(self, filename):
 
337
        b = Branch(filename)
 
338
        i = b.inventory.path2id(b.relpath(filename))
 
339
        if i == None:
 
340
            bailout("%r is not a versioned file" % filename)
 
341
        else:
 
342
            print i
 
343
 
 
344
 
 
345
class cmd_file_path(Command):
 
346
    """Print path of file_ids to a file or directory.
 
347
 
 
348
    This prints one line for each directory down to the target,
 
349
    starting at the branch root."""
 
350
    hidden = True
 
351
    takes_args = ['filename']
 
352
    def run(self, filename):
 
353
        b = Branch(filename)
 
354
        inv = b.inventory
 
355
        fid = inv.path2id(b.relpath(filename))
 
356
        if fid == None:
 
357
            bailout("%r is not a versioned file" % filename)
 
358
        for fip in inv.get_idpath(fid):
 
359
            print fip
 
360
 
 
361
 
 
362
class cmd_revision_history(Command):
 
363
    """Display list of revision ids on this branch."""
 
364
    def run(self):
 
365
        for patchid in Branch('.').revision_history():
 
366
            print patchid
 
367
 
 
368
 
 
369
class cmd_directories(Command):
 
370
    """Display list of versioned directories in this branch."""
 
371
    def run(self):
 
372
        for name, ie in Branch('.').read_working_inventory().directories():
 
373
            if name == '':
 
374
                print '.'
 
375
            else:
 
376
                print name
 
377
 
 
378
 
 
379
class cmd_init(Command):
 
380
    """Make a directory into a versioned branch.
 
381
 
 
382
    Use this to create an empty branch, or before importing an
 
383
    existing project.
 
384
 
 
385
    Recipe for importing a tree of files:
 
386
        cd ~/project
 
387
        bzr init
 
388
        bzr add -v .
 
389
        bzr status
 
390
        bzr commit -m 'imported project'
 
391
    """
 
392
    def run(self):
 
393
        Branch('.', init=True)
 
394
 
 
395
 
 
396
class cmd_diff(Command):
 
397
    """Show differences in working tree.
 
398
    
 
399
    If files are listed, only the changes in those files are listed.
 
400
    Otherwise, all changes for the tree are listed.
 
401
 
 
402
    TODO: Given two revision arguments, show the difference between them.
 
403
 
 
404
    TODO: Allow diff across branches.
 
405
 
 
406
    TODO: Option to use external diff command; could be GNU diff, wdiff,
 
407
          or a graphical diff.
 
408
 
 
409
    TODO: Python difflib is not exactly the same as unidiff; should
 
410
          either fix it up or prefer to use an external diff.
 
411
 
 
412
    TODO: If a directory is given, diff everything under that.
 
413
 
 
414
    TODO: Selected-file diff is inefficient and doesn't show you
 
415
          deleted files.
 
416
 
 
417
    TODO: This probably handles non-Unix newlines poorly.
 
418
    """
 
419
    
 
420
    takes_args = ['file*']
 
421
    takes_options = ['revision']
 
422
 
 
423
    def run(self, revision=None, file_list=None):
 
424
        from bzrlib.diff import show_diff
 
425
    
 
426
        show_diff(Branch('.'), revision, file_list)
 
427
 
 
428
 
 
429
class cmd_deleted(Command):
 
430
    """List files deleted in the working tree.
 
431
 
 
432
    TODO: Show files deleted since a previous revision, or between two revisions.
 
433
    """
 
434
    def run(self, show_ids=False):
 
435
        b = Branch('.')
 
436
        old = b.basis_tree()
 
437
        new = b.working_tree()
 
438
 
 
439
        ## TODO: Much more efficient way to do this: read in new
 
440
        ## directories with readdir, rather than stating each one.  Same
 
441
        ## level of effort but possibly much less IO.  (Or possibly not,
 
442
        ## if the directories are very large...)
 
443
 
 
444
        for path, ie in old.inventory.iter_entries():
 
445
            if not new.has_id(ie.file_id):
 
446
                if show_ids:
 
447
                    print '%-50s %s' % (path, ie.file_id)
 
448
                else:
 
449
                    print path
 
450
 
 
451
class cmd_root(Command):
 
452
    """Show the tree root directory.
 
453
 
 
454
    The root is the nearest enclosing directory with a .bzr control
 
455
    directory."""
 
456
    takes_args = ['filename?']
 
457
    def run(self, filename=None):
 
458
        """Print the branch root."""
 
459
        print bzrlib.branch.find_branch_root(filename)
 
460
 
 
461
 
 
462
 
 
463
class cmd_log(Command):
 
464
    """Show log of this branch.
 
465
 
 
466
    TODO: Options to show ids; to limit range; etc.
 
467
    """
 
468
    takes_options = ['timezone', 'verbose']
 
469
    def run(self, timezone='original', verbose=False):
 
470
        Branch('.').write_log(show_timezone=timezone, verbose=verbose)
 
471
 
 
472
 
 
473
class cmd_ls(Command):
 
474
    """List files in a tree.
 
475
 
 
476
    TODO: Take a revision or remote path and list that tree instead.
 
477
    """
 
478
    hidden = True
 
479
    def run(self, revision=None, verbose=False):
 
480
        b = Branch('.')
 
481
        if revision == None:
 
482
            tree = b.working_tree()
 
483
        else:
 
484
            tree = b.revision_tree(b.lookup_revision(revision))
 
485
 
 
486
        for fp, fc, kind, fid in tree.list_files():
 
487
            if verbose:
 
488
                if kind == 'directory':
 
489
                    kindch = '/'
 
490
                elif kind == 'file':
 
491
                    kindch = ''
 
492
                else:
 
493
                    kindch = '???'
 
494
 
 
495
                print '%-8s %s%s' % (fc, fp, kindch)
 
496
            else:
 
497
                print fp
 
498
 
 
499
 
 
500
 
 
501
class cmd_unknowns(Command):
 
502
    """List unknown files"""
 
503
    def run(self):
 
504
        for f in Branch('.').unknowns():
 
505
            print quotefn(f)
 
506
 
 
507
 
 
508
 
 
509
class cmd_ignore(Command):
 
510
    """Ignore a command or pattern"""
 
511
    takes_args = ['name_pattern']
 
512
    
 
513
    def run(self, name_pattern):
 
514
        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()
 
520
 
 
521
        inv = b.working_tree().inventory
 
522
        if inv.path2id('.bzrignore'):
 
523
            mutter('.bzrignore is already versioned')
 
524
        else:
 
525
            mutter('need to make new .bzrignore file versioned')
 
526
            b.add(['.bzrignore'])
 
527
 
 
528
 
 
529
 
 
530
class cmd_ignored(Command):
 
531
    """List ignored files and the patterns that matched them."""
 
532
    def run(self):
 
533
        tree = Branch('.').working_tree()
 
534
        for path, file_class, kind, file_id in tree.list_files():
 
535
            if file_class != 'I':
 
536
                continue
 
537
            ## XXX: Slightly inefficient since this was already calculated
 
538
            pat = tree.is_ignored(path)
 
539
            print '%-50s %s' % (path, pat)
 
540
 
 
541
 
 
542
class cmd_lookup_revision(Command):
 
543
    """Lookup the revision-id from a revision-number
 
544
 
 
545
    example:
 
546
        bzr lookup-revision 33
 
547
        """
 
548
    hidden = True
 
549
    def run(self, revno):
 
550
        try:
 
551
            revno = int(revno)
 
552
        except ValueError:
 
553
            raise BzrError("not a valid revision-number: %r" % revno)
 
554
 
 
555
        print Branch('.').lookup_revision(revno) or NONE_STRING
 
556
 
 
557
 
 
558
 
 
559
class cmd_export(Command):
 
560
    """Export past revision to destination directory.
 
561
 
 
562
    If no revision is specified this exports the last committed revision."""
 
563
    takes_args = ['dest']
 
564
    takes_options = ['revision']
 
565
    def run(self, dest, revno=None):
 
566
        b = Branch('.')
 
567
        if revno == None:
 
568
            rh = b.revision_history[-1]
 
569
        else:
 
570
            rh = b.lookup_revision(int(revno))
 
571
        t = b.revision_tree(rh)
 
572
        t.export(dest)
 
573
 
 
574
 
 
575
class cmd_cat(Command):
 
576
    """Write a file's text from a previous revision."""
 
577
 
 
578
    takes_options = ['revision']
 
579
    takes_args = ['filename']
 
580
 
 
581
    def run(self, filename, revision=None):
 
582
        if revision == None:
 
583
            raise BzrCommandError("bzr cat requires a revision number")
 
584
        b = Branch('.')
 
585
        b.print_file(b.relpath(filename), int(revision))
 
586
 
 
587
 
 
588
class cmd_local_time_offset(Command):
 
589
    """Show the offset in seconds from GMT to local time."""
 
590
    hidden = True    
 
591
    def run(self):
 
592
        print bzrlib.osutils.local_time_offset()
 
593
 
 
594
 
 
595
 
 
596
class cmd_commit(Command):
 
597
    """Commit changes into a new revision.
 
598
 
 
599
    TODO: Commit only selected files.
 
600
 
 
601
    TODO: Run hooks on tree to-be-committed, and after commit.
 
602
 
 
603
    TODO: Strict commit that fails if there are unknown or deleted files.
 
604
    """
 
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)
 
611
 
 
612
 
 
613
class cmd_check(Command):
 
614
    """Validate consistency of branch history.
 
615
 
 
616
    This command checks various invariants about the branch storage to
 
617
    detect data corruption or bzr bugs.
 
618
    """
 
619
    takes_args = ['dir?']
 
620
    def run(self, dir='.'):
 
621
        import bzrlib.check
 
622
        bzrlib.check.check(Branch(dir, find_root=False))
 
623
 
 
624
 
 
625
 
 
626
class cmd_whoami(Command):
 
627
    """Show bzr user id."""
 
628
    takes_options = ['email']
 
629
    
 
630
    def run(self, email=False):
 
631
        if email:
 
632
            print bzrlib.osutils.user_email()
 
633
        else:
 
634
            print bzrlib.osutils.username()
 
635
 
 
636
 
 
637
class cmd_selftest(Command):
 
638
    """Run internal test suite"""
 
639
    hidden = True
 
640
    def run(self):
 
641
        failures, tests = 0, 0
 
642
 
 
643
        import doctest, bzrlib.store, bzrlib.tests
 
644
        bzrlib.trace.verbose = False
 
645
 
 
646
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
647
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
648
            mf, mt = doctest.testmod(m)
 
649
            failures += mf
 
650
            tests += mt
 
651
            print '%-40s %3d tests' % (m.__name__, mt),
 
652
            if mf:
 
653
                print '%3d FAILED!' % mf
 
654
            else:
 
655
                print
 
656
 
 
657
        print '%-40s %3d tests' % ('total', tests),
 
658
        if failures:
 
659
            print '%3d FAILED!' % failures
 
660
        else:
 
661
            print
 
662
 
 
663
 
 
664
 
 
665
class cmd_version(Command):
 
666
    """Show version of bzr"""
 
667
    def run(self):
 
668
        show_version()
 
669
 
 
670
def show_version():
 
671
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
672
    print bzrlib.__copyright__
 
673
    print "http://bazaar-ng.org/"
 
674
    print
 
675
    print "bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and"
 
676
    print "you may use, modify and redistribute it under the terms of the GNU"
 
677
    print "General Public License version 2 or later."
 
678
 
 
679
 
 
680
class cmd_rocks(Command):
 
681
    """Statement of optimism."""
 
682
    hidden = True
 
683
    def run(self):
 
684
        print "it sure does!"
 
685
 
 
686
 
 
687
class cmd_assert_fail(Command):
 
688
    """Test reporting of assertion failures"""
 
689
    hidden = True
 
690
    def run(self):
 
691
        assert False, "always fails"
 
692
 
 
693
 
 
694
class cmd_help(Command):
 
695
    """Show help on a command or other topic.
 
696
 
 
697
    For a list of all available commands, say 'bzr help commands'."""
 
698
    takes_args = ['topic?']
 
699
    
 
700
    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
    if cmdclass.takes_options:
 
745
        print
 
746
        print 'options:'
 
747
        for on in cmdclass.takes_options:
 
748
            print '    --%s' % on
 
749
 
 
750
 
 
751
def help_commands():
 
752
    """List all commands"""
 
753
    import inspect
 
754
    
 
755
    accu = []
 
756
    for k, v in globals().items():
 
757
        if k.startswith('cmd_'):
 
758
            accu.append((k[4:].replace('_','-'), v))
 
759
    accu.sort()
 
760
    for cmdname, cmdclass in accu:
 
761
        if cmdclass.hidden:
 
762
            continue
 
763
        print cmdname
 
764
        help = inspect.getdoc(cmdclass)
 
765
        if help:
 
766
            print "    " + help.split('\n', 1)[0]
 
767
 
 
768
 
 
769
######################################################################
 
770
# main routine
339
771
 
340
772
 
341
773
# list of all available options; the rhs can be either None for an
343
775
# the type.
344
776
OPTIONS = {
345
777
    'all':                    None,
346
 
    'basis':                  str,
347
 
    'diff-options':           str,
348
778
    'help':                   None,
349
 
    'file':                   unicode,
350
 
    'force':                  None,
351
 
    'format':                 unicode,
352
 
    'forward':                None,
353
 
    'quiet':                  None,
354
779
    'message':                unicode,
355
 
    'no-recurse':             None,
356
780
    'profile':                None,
357
 
    'revision':               _parse_revision_str,
358
 
    'short':                  None,
 
781
    'revision':               int,
359
782
    'show-ids':               None,
360
783
    'timezone':               str,
361
784
    'verbose':                None,
362
785
    'version':                None,
363
786
    'email':                  None,
364
 
    'unchanged':              None,
365
 
    'update':                 None,
366
 
    'long':                   None,
367
 
    'root':                   str,
368
 
    'no-backup':              None,
369
 
    'pattern':                str,
370
 
    'remember':               None,
371
787
    }
372
788
 
373
789
SHORT_OPTIONS = {
374
 
    'F':                      'file', 
375
 
    'h':                      'help',
376
790
    'm':                      'message',
377
791
    'r':                      'revision',
378
792
    'v':                      'verbose',
379
 
    'l':                      'long',
380
 
    'q':                      'quiet',
381
793
}
382
794
 
383
795
 
391
803
 
392
804
    >>> parse_args('--help'.split())
393
805
    ([], {'help': True})
394
 
    >>> parse_args('help -- --invalidcmd'.split())
395
 
    (['help', '--invalidcmd'], {})
396
806
    >>> parse_args('--version'.split())
397
807
    ([], {'version': True})
398
808
    >>> parse_args('status --all'.split())
399
809
    (['status'], {'all': True})
400
810
    >>> parse_args('commit --message=biter'.split())
401
811
    (['commit'], {'message': u'biter'})
402
 
    >>> parse_args('log -r 500'.split())
403
 
    (['log'], {'revision': [<RevisionSpec_int 500>]})
404
 
    >>> parse_args('log -r500..600'.split())
405
 
    (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
406
 
    >>> parse_args('log -vr500..600'.split())
407
 
    (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
408
 
    >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
409
 
    (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
410
812
    """
411
813
    args = []
412
814
    opts = {}
413
815
 
414
 
    argsover = False
 
816
    # TODO: Maybe handle '--' to end options?
 
817
 
415
818
    while argv:
416
819
        a = argv.pop(0)
417
 
        if not argsover and a[0] == '-':
 
820
        if a[0] == '-':
418
821
            # option names must not be unicode
419
822
            a = str(a)
420
823
            optarg = None
421
824
            if a[1] == '-':
422
 
                if a == '--':
423
 
                    # We've received a standalone -- No more flags
424
 
                    argsover = True
425
 
                    continue
426
825
                mutter("  got option %r" % a)
427
826
                if '=' in a:
428
827
                    optname, optarg = a[2:].split('=', 1)
429
828
                else:
430
829
                    optname = a[2:]
431
830
                if optname not in OPTIONS:
432
 
                    raise BzrError('unknown long option %r' % a)
 
831
                    bailout('unknown long option %r' % a)
433
832
            else:
434
833
                shortopt = a[1:]
435
 
                if shortopt in SHORT_OPTIONS:
436
 
                    # Multi-character options must have a space to delimit
437
 
                    # their value
438
 
                    optname = SHORT_OPTIONS[shortopt]
439
 
                else:
440
 
                    # Single character short options, can be chained,
441
 
                    # and have their value appended to their name
442
 
                    shortopt = a[1:2]
443
 
                    if shortopt not in SHORT_OPTIONS:
444
 
                        # We didn't find the multi-character name, and we
445
 
                        # didn't find the single char name
446
 
                        raise BzrError('unknown short option %r' % a)
447
 
                    optname = SHORT_OPTIONS[shortopt]
448
 
 
449
 
                    if a[2:]:
450
 
                        # There are extra things on this option
451
 
                        # see if it is the value, or if it is another
452
 
                        # short option
453
 
                        optargfn = OPTIONS[optname]
454
 
                        if optargfn is None:
455
 
                            # This option does not take an argument, so the
456
 
                            # next entry is another short option, pack it back
457
 
                            # into the list
458
 
                            argv.insert(0, '-' + a[2:])
459
 
                        else:
460
 
                            # This option takes an argument, so pack it
461
 
                            # into the array
462
 
                            optarg = a[2:]
 
834
                if shortopt not in SHORT_OPTIONS:
 
835
                    bailout('unknown short option %r' % a)
 
836
                optname = SHORT_OPTIONS[shortopt]
463
837
            
464
838
            if optname in opts:
465
839
                # XXX: Do we ever want to support this, e.g. for -r?
466
 
                raise BzrError('repeated option %r' % a)
 
840
                bailout('repeated option %r' % a)
467
841
                
468
842
            optargfn = OPTIONS[optname]
469
843
            if optargfn:
470
844
                if optarg == None:
471
845
                    if not argv:
472
 
                        raise BzrError('option %r needs an argument' % a)
 
846
                        bailout('option %r needs an argument' % a)
473
847
                    else:
474
848
                        optarg = argv.pop(0)
475
849
                opts[optname] = optargfn(optarg)
476
850
            else:
477
851
                if optarg != None:
478
 
                    raise BzrError('option %r takes no argument' % optname)
 
852
                    bailout('option %r takes no argument' % optname)
479
853
                opts[optname] = True
480
854
        else:
481
855
            args.append(a)
530
904
 
531
905
 
532
906
 
533
 
def apply_profiled(the_callable, *args, **kwargs):
534
 
    import hotshot
535
 
    import tempfile
536
 
    import hotshot.stats
537
 
    pffileno, pfname = tempfile.mkstemp()
538
 
    try:
539
 
        prof = hotshot.Profile(pfname)
540
 
        try:
541
 
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
542
 
        finally:
543
 
            prof.close()
544
 
        stats = hotshot.stats.load(pfname)
545
 
        stats.strip_dirs()
546
 
        stats.sort_stats('cum')   # 'time'
547
 
        ## XXX: Might like to write to stderr or the trace file instead but
548
 
        ## print_stats seems hardcoded to stdout
549
 
        stats.print_stats(20)
550
 
        return ret
551
 
    finally:
552
 
        os.close(pffileno)
553
 
        os.remove(pfname)
554
 
 
555
 
 
556
907
def run_bzr(argv):
557
908
    """Execute a command.
558
909
 
559
910
    This is similar to main(), but without all the trappings for
560
911
    logging and error handling.  
561
 
    
562
 
    argv
563
 
       The command-line arguments, without the program name from argv[0]
564
 
    
565
 
    Returns a command status or raises an exception.
566
 
 
567
 
    Special master options: these must come before the command because
568
 
    they control how the command is interpreted.
569
 
 
570
 
    --no-plugins
571
 
        Do not load plugin modules at all
572
 
 
573
 
    --builtin
574
 
        Only use builtin commands.  (Plugins are still allowed to change
575
 
        other behaviour.)
576
 
 
577
 
    --profile
578
 
        Run under the Python profiler.
579
912
    """
580
 
    # Load all of the transport methods
581
 
    import bzrlib.transport.local, bzrlib.transport.http
582
 
    
 
913
 
583
914
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
584
 
 
585
 
    opt_profile = opt_no_plugins = opt_builtin = False
586
 
 
587
 
    # --no-plugins is handled specially at a very early stage. We need
588
 
    # to load plugins before doing other command parsing so that they
589
 
    # can override commands, but this needs to happen first.
590
 
 
591
 
    for a in argv:
592
 
        if a == '--profile':
593
 
            opt_profile = True
594
 
        elif a == '--no-plugins':
595
 
            opt_no_plugins = True
596
 
        elif a == '--builtin':
597
 
            opt_builtin = True
598
 
        else:
599
 
            break
600
 
        argv.remove(a)
601
 
 
602
 
    if (not argv) or (argv[0] == '--help'):
603
 
        from bzrlib.help import help
604
 
        if len(argv) > 1:
605
 
            help(argv[1])
606
 
        else:
607
 
            help()
608
 
        return 0
609
 
 
610
 
    if argv[0] == '--version':
611
 
        from bzrlib.builtins import show_version
612
 
        show_version()
613
 
        return 0
614
 
        
615
 
    if not opt_no_plugins:
616
 
        from bzrlib.plugin import load_plugins
617
 
        load_plugins()
618
 
 
619
 
    cmd = str(argv.pop(0))
620
 
 
621
 
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
622
 
 
623
 
    if opt_profile:
624
 
        ret = apply_profiled(cmd_obj.run_argv, argv)
625
 
    else:
626
 
        ret = cmd_obj.run_argv(argv)
627
 
    return ret or 0
 
915
    
 
916
    try:
 
917
        args, opts = parse_args(argv[1:])
 
918
        if 'help' in opts:
 
919
            if args:
 
920
                help(args[0])
 
921
            else:
 
922
                help()
 
923
            return 0
 
924
        elif 'version' in opts:
 
925
            cmd_version([], [])
 
926
            return 0
 
927
        cmd = str(args.pop(0))
 
928
    except IndexError:
 
929
        log_error('usage: bzr COMMAND')
 
930
        log_error('  try "bzr help"')
 
931
        return 1
 
932
 
 
933
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
934
 
 
935
    # global option
 
936
    if 'profile' in opts:
 
937
        profile = True
 
938
        del opts['profile']
 
939
    else:
 
940
        profile = False
 
941
 
 
942
    # check options are reasonable
 
943
    allowed = cmd_class.takes_options
 
944
    for oname in opts:
 
945
        if oname not in allowed:
 
946
            raise BzrCommandError("option %r is not allowed for command %r"
 
947
                                  % (oname, cmd))
 
948
 
 
949
    # mix arguments and options into one dictionary
 
950
    cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
 
951
    cmdopts = {}
 
952
    for k, v in opts.items():
 
953
        cmdopts[k.replace('-', '_')] = v
 
954
 
 
955
    if profile:
 
956
        import hotshot
 
957
        pffileno, pfname = tempfile.mkstemp()
 
958
        try:
 
959
            prof = hotshot.Profile(pfname)
 
960
            ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
 
961
            prof.close()
 
962
 
 
963
            import hotshot.stats
 
964
            stats = hotshot.stats.load(pfname)
 
965
            #stats.strip_dirs()
 
966
            stats.sort_stats('time')
 
967
            ## XXX: Might like to write to stderr or the trace file instead but
 
968
            ## print_stats seems hardcoded to stdout
 
969
            stats.print_stats(20)
 
970
            
 
971
            return ret
 
972
 
 
973
        finally:
 
974
            os.close(pffileno)
 
975
            os.remove(pfname)
 
976
    else:
 
977
        cmdobj = cmd_class(cmdopts, cmdargs) or 0
 
978
 
 
979
 
 
980
 
 
981
def _report_exception(e, summary, quiet=False):
 
982
    import traceback
 
983
    log_error('bzr: ' + summary)
 
984
    bzrlib.trace.log_exception(e)
 
985
 
 
986
    if not quiet:
 
987
        tb = sys.exc_info()[2]
 
988
        exinfo = traceback.extract_tb(tb)
 
989
        if exinfo:
 
990
            sys.stderr.write('  at %s:%d in %s()\n' % exinfo[-1][:3])
 
991
        sys.stderr.write('  see ~/.bzr.log for debug information\n')
 
992
 
628
993
 
629
994
 
630
995
def main(argv):
631
 
    import bzrlib.ui
632
 
    bzrlib.trace.log_startup(argv)
633
 
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
634
 
 
635
 
    return run_bzr_catch_errors(argv[1:])
636
 
 
637
 
 
638
 
def run_bzr_catch_errors(argv):
 
996
    import errno
 
997
    
 
998
    bzrlib.trace.create_tracefile(argv)
 
999
 
639
1000
    try:
640
1001
        try:
641
 
            return run_bzr(argv)
642
 
        finally:
643
 
            # do this here inside the exception wrappers to catch EPIPE
 
1002
            ret = run_bzr(argv)
 
1003
            # do this here to catch EPIPE
644
1004
            sys.stdout.flush()
645
 
    except BzrCommandError, e:
646
 
        # command line syntax error, etc
647
 
        log_error(str(e))
648
 
        return 1
649
 
    except BzrError, e:
650
 
        bzrlib.trace.log_exception()
651
 
        return 1
652
 
    except AssertionError, e:
653
 
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
654
 
        return 3
655
 
    except KeyboardInterrupt, e:
656
 
        bzrlib.trace.log_exception('interrupted')
657
 
        return 2
658
 
    except Exception, e:
659
 
        import errno
660
 
        if (isinstance(e, IOError) 
661
 
            and hasattr(e, 'errno')
662
 
            and e.errno == errno.EPIPE):
663
 
            bzrlib.trace.note('broken pipe')
664
 
            return 2
665
 
        else:
666
 
            ## import pdb
667
 
            ## pdb.pm()
668
 
            bzrlib.trace.log_exception()
669
 
            return 2
 
1005
            return ret
 
1006
        except BzrError, e:
 
1007
            quiet = isinstance(e, (BzrCommandError))
 
1008
            _report_exception(e, 'error: ' + e.args[0], quiet=quiet)
 
1009
            if len(e.args) > 1:
 
1010
                for h in e.args[1]:
 
1011
                    # some explanation or hints
 
1012
                    log_error('  ' + h)
 
1013
            return 1
 
1014
        except AssertionError, e:
 
1015
            msg = 'assertion failed'
 
1016
            if str(e):
 
1017
                msg += ': ' + str(e)
 
1018
            _report_exception(e, msg)
 
1019
            return 2
 
1020
        except KeyboardInterrupt, e:
 
1021
            _report_exception(e, 'interrupted', quiet=True)
 
1022
            return 2
 
1023
        except Exception, e:
 
1024
            quiet = False
 
1025
            if isinstance(e, IOError) and e.errno == errno.EPIPE:
 
1026
                quiet = True
 
1027
                msg = 'broken pipe'
 
1028
            else:
 
1029
                msg = str(e).rstrip('\n')
 
1030
            _report_exception(e, msg, quiet)
 
1031
            return 2
 
1032
    finally:
 
1033
        bzrlib.trace.close_trace()
 
1034
 
670
1035
 
671
1036
if __name__ == '__main__':
672
1037
    sys.exit(main(sys.argv))