~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-23 23:52:10 UTC
  • Revision ID: mbp@sourcefrog.net-20050323235210-5464746b93c39ed0
more notes on darcs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 by Canonical Ltd
 
1
#! /usr/bin/python
 
2
 
 
3
 
 
4
# Copyright (C) 2004, 2005 by Martin Pool
 
5
# Copyright (C) 2005 by Canonical Ltd
 
6
 
2
7
 
3
8
# This program is free software; you can redistribute it and/or modify
4
9
# it under the terms of the GNU General Public License as published by
14
19
# along with this program; if not, write to the Free Software
15
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
21
 
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
 
 
28
 
# TODO: Help messages for options.
29
 
 
30
 
# TODO: Define arguments by objects, rather than just using names.
31
 
# Those objects can specify the expected type of the argument, which
32
 
# would help with validation and shell completion.
33
 
 
34
 
 
35
 
 
36
 
import sys
37
 
import os
38
 
from warnings import warn
39
 
from inspect import getdoc
 
22
"""Bazaar-NG -- a free distributed version-control tool
 
23
 
 
24
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
 
25
 
 
26
Current limitation include:
 
27
 
 
28
* Metadata format is not stable yet -- you may need to
 
29
  discard history in the future.
 
30
 
 
31
* No handling of subdirectories, symlinks or any non-text files.
 
32
 
 
33
* Insufficient error handling.
 
34
 
 
35
* Many commands unimplemented or partially implemented.
 
36
 
 
37
* Space-inefficient storage.
 
38
 
 
39
* No merge operators yet.
 
40
 
 
41
Interesting commands::
 
42
 
 
43
  bzr help
 
44
       Show summary help screen
 
45
  bzr version
 
46
       Show software version/licence/non-warranty.
 
47
  bzr init
 
48
       Start versioning the current directory
 
49
  bzr add FILE...
 
50
       Make files versioned.
 
51
  bzr log
 
52
       Show revision history.
 
53
  bzr diff
 
54
       Show changes from last revision to working copy.
 
55
  bzr commit -m 'MESSAGE'
 
56
       Store current state as new revision.
 
57
  bzr export REVNO DESTINATION
 
58
       Export the branch state at a previous version.
 
59
  bzr status
 
60
       Show summary of pending changes.
 
61
  bzr remove FILE...
 
62
       Make a file not versioned.
 
63
"""
 
64
 
 
65
# not currently working:
 
66
#  bzr info
 
67
#       Show some information about this branch.
 
68
 
 
69
 
 
70
 
 
71
__copyright__ = "Copyright 2005 Canonical Development Ltd."
 
72
__author__ = "Martin Pool <mbp@canonical.com>"
 
73
__docformat__ = "restructuredtext en"
 
74
__version__ = '0.0.0'
 
75
 
 
76
 
 
77
import sys, os, random, time, sha, sets, types, re, shutil, tempfile
 
78
import traceback, socket, fnmatch, difflib
 
79
from os import path
 
80
from sets import Set
 
81
from pprint import pprint
 
82
from stat import *
 
83
from glob import glob
40
84
 
41
85
import bzrlib
42
 
import bzrlib.trace
43
 
from bzrlib.trace import mutter, note, log_error, warning
44
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
45
 
from bzrlib.revisionspec import RevisionSpec
46
 
from bzrlib import BZRDIR
47
 
 
48
 
plugin_cmds = {}
49
 
 
50
 
 
51
 
def register_command(cmd):
52
 
    "Utility function to help register a command"
53
 
    global plugin_cmds
54
 
    k = cmd.__name__
55
 
    if k.startswith("cmd_"):
56
 
        k_unsquished = _unsquish_command_name(k)
57
 
    else:
58
 
        k_unsquished = k
59
 
    if not plugin_cmds.has_key(k_unsquished):
60
 
        plugin_cmds[k_unsquished] = cmd
61
 
        mutter('registered plugin command %s', k_unsquished)      
62
 
    else:
63
 
        log_error('Two plugins defined the same command: %r' % k)
64
 
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
65
 
 
66
 
 
67
 
def _squish_command_name(cmd):
68
 
    return 'cmd_' + cmd.replace('-', '_')
69
 
 
70
 
 
71
 
def _unsquish_command_name(cmd):
72
 
    assert cmd.startswith("cmd_")
73
 
    return cmd[4:].replace('_','-')
74
 
 
75
 
 
76
 
def _parse_revision_str(revstr):
77
 
    """This handles a revision string -> revno.
78
 
 
79
 
    This always returns a list.  The list will have one element for
80
 
    each revision.
81
 
 
82
 
    >>> _parse_revision_str('234')
83
 
    [<RevisionSpec_int 234>]
84
 
    >>> _parse_revision_str('234..567')
85
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 567>]
86
 
    >>> _parse_revision_str('..')
87
 
    [<RevisionSpec None>, <RevisionSpec None>]
88
 
    >>> _parse_revision_str('..234')
89
 
    [<RevisionSpec None>, <RevisionSpec_int 234>]
90
 
    >>> _parse_revision_str('234..')
91
 
    [<RevisionSpec_int 234>, <RevisionSpec None>]
92
 
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
93
 
    [<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
94
 
    >>> _parse_revision_str('234....789') # Error?
95
 
    [<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
96
 
    >>> _parse_revision_str('revid:test@other.com-234234')
97
 
    [<RevisionSpec_revid revid:test@other.com-234234>]
98
 
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
99
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
100
 
    >>> _parse_revision_str('revid:test@other.com-234234..23')
101
 
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
102
 
    >>> _parse_revision_str('date:2005-04-12')
103
 
    [<RevisionSpec_date date:2005-04-12>]
104
 
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
105
 
    [<RevisionSpec_date date:2005-04-12 12:24:33>]
106
 
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
107
 
    [<RevisionSpec_date date:2005-04-12T12:24:33>]
108
 
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
109
 
    [<RevisionSpec_date date:2005-04-12,12:24:33>]
110
 
    >>> _parse_revision_str('-5..23')
111
 
    [<RevisionSpec_int -5>, <RevisionSpec_int 23>]
112
 
    >>> _parse_revision_str('-5')
113
 
    [<RevisionSpec_int -5>]
114
 
    >>> _parse_revision_str('123a')
115
 
    Traceback (most recent call last):
116
 
      ...
117
 
    BzrError: No namespace registered for string: '123a'
118
 
    >>> _parse_revision_str('abc')
119
 
    Traceback (most recent call last):
120
 
      ...
121
 
    BzrError: No namespace registered for string: 'abc'
122
 
    """
123
 
    import re
124
 
    old_format_re = re.compile('\d*:\d*')
125
 
    m = old_format_re.match(revstr)
126
 
    revs = []
127
 
    if m:
128
 
        warning('Colon separator for revision numbers is deprecated.'
129
 
                ' Use .. instead')
130
 
        for rev in revstr.split(':'):
131
 
            if rev:
132
 
                revs.append(RevisionSpec(int(rev)))
133
 
            else:
134
 
                revs.append(RevisionSpec(None))
135
 
    else:
136
 
        for x in revstr.split('..'):
137
 
            if not x:
138
 
                revs.append(RevisionSpec(None))
139
 
            else:
140
 
                revs.append(RevisionSpec(x))
141
 
    return revs
142
 
 
143
 
 
144
 
def get_merge_type(typestring):
145
 
    """Attempt to find the merge class/factory associated with a string."""
146
 
    from merge import merge_types
147
 
    try:
148
 
        return merge_types[typestring][0]
149
 
    except KeyError:
150
 
        templ = '%s%%7s: %%s' % (' '*12)
151
 
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
152
 
        type_list = '\n'.join(lines)
153
 
        msg = "No known merge type %s. Supported types are:\n%s" %\
154
 
            (typestring, type_list)
155
 
        raise BzrCommandError(msg)
156
 
 
157
 
 
158
 
def _builtin_commands():
159
 
    import bzrlib.builtins
160
 
    r = {}
161
 
    builtins = bzrlib.builtins.__dict__
162
 
    for name in builtins:
163
 
        if name.startswith("cmd_"):
164
 
            real_name = _unsquish_command_name(name)        
165
 
            r[real_name] = builtins[name]
166
 
    return r
167
 
 
168
 
            
169
 
 
170
 
def builtin_command_names():
171
 
    """Return list of builtin command names."""
172
 
    return _builtin_commands().keys()
173
 
    
174
 
 
175
 
def plugin_command_names():
176
 
    return plugin_cmds.keys()
177
 
 
178
 
 
179
 
def _get_cmd_dict(plugins_override=True):
180
 
    """Return name->class mapping for all commands."""
181
 
    d = _builtin_commands()
182
 
    if plugins_override:
183
 
        d.update(plugin_cmds)
184
 
    return d
185
 
 
186
 
    
187
 
def get_all_cmds(plugins_override=True):
188
 
    """Return canonical name and class for all registered commands."""
189
 
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
190
 
        yield k,v
191
 
 
192
 
 
193
 
def get_cmd_object(cmd_name, plugins_override=True):
194
 
    """Return the canonical name and command class for a command.
195
 
 
196
 
    plugins_override
197
 
        If true, plugin commands can override builtins.
198
 
    """
199
 
    from bzrlib.externalcommand import ExternalCommand
200
 
 
201
 
    cmd_name = str(cmd_name)            # not unicode
202
 
 
203
 
    # first look up this command under the specified name
204
 
    cmds = _get_cmd_dict(plugins_override=plugins_override)
205
 
    try:
206
 
        return cmds[cmd_name]()
207
 
    except KeyError:
208
 
        pass
209
 
 
210
 
    # look for any command which claims this as an alias
211
 
    for real_cmd_name, cmd_class in cmds.iteritems():
212
 
        if cmd_name in cmd_class.aliases:
213
 
            return cmd_class()
214
 
 
215
 
    cmd_obj = ExternalCommand.find_command(cmd_name)
216
 
    if cmd_obj:
217
 
        return cmd_obj
218
 
 
219
 
    raise BzrCommandError("unknown command %r" % cmd_name)
220
 
 
221
 
 
222
 
class Command(object):
223
 
    """Base class for commands.
224
 
 
225
 
    Commands are the heart of the command-line bzr interface.
226
 
 
227
 
    The command object mostly handles the mapping of command-line
228
 
    parameters into one or more bzrlib operations, and of the results
229
 
    into textual output.
230
 
 
231
 
    Commands normally don't have any state.  All their arguments are
232
 
    passed in to the run method.  (Subclasses may take a different
233
 
    policy if the behaviour of the instance needs to depend on e.g. a
234
 
    shell plugin and not just its Python class.)
235
 
 
236
 
    The docstring for an actual command should give a single-line
237
 
    summary, then a complete description of the command.  A grammar
238
 
    description will be inserted.
239
 
 
240
 
    aliases
241
 
        Other accepted names for this command.
242
 
 
243
 
    takes_args
244
 
        List of argument forms, marked with whether they are optional,
245
 
        repeated, etc.
246
 
 
247
 
    takes_options
248
 
        List of options that may be given for this command.
249
 
 
250
 
    hidden
251
 
        If true, this command isn't advertised.  This is typically
252
 
        for commands intended for expert users.
253
 
    """
254
 
    aliases = []
255
 
    
256
 
    takes_args = []
257
 
    takes_options = []
258
 
 
259
 
    hidden = False
260
 
    
261
 
    def __init__(self):
262
 
        """Construct an instance of this command."""
263
 
        if self.__doc__ == Command.__doc__:
264
 
            warn("No help message set for %r" % self)
265
 
 
266
 
 
267
 
    def run_argv(self, argv):
268
 
        """Parse command line and run."""
269
 
        args, opts = parse_args(argv)
270
 
 
271
 
        if 'help' in opts:  # e.g. bzr add --help
272
 
            from bzrlib.help import help_on_command
273
 
            help_on_command(self.name())
274
 
            return 0
275
 
 
276
 
        # check options are reasonable
277
 
        allowed = self.takes_options
278
 
        for oname in opts:
279
 
            if oname not in allowed:
280
 
                raise BzrCommandError("option '--%s' is not allowed for command %r"
281
 
                                      % (oname, self.name()))
282
 
 
283
 
        # mix arguments and options into one dictionary
284
 
        cmdargs = _match_argform(self.name(), self.takes_args, args)
285
 
        cmdopts = {}
286
 
        for k, v in opts.items():
287
 
            cmdopts[k.replace('-', '_')] = v
288
 
 
289
 
        all_cmd_args = cmdargs.copy()
290
 
        all_cmd_args.update(cmdopts)
291
 
 
292
 
        return self.run(**all_cmd_args)
293
 
 
294
 
    
295
 
    def run(self):
296
 
        """Actually run the command.
297
 
 
298
 
        This is invoked with the options and arguments bound to
299
 
        keyword parameters.
300
 
 
301
 
        Return 0 or None if the command was successful, or a non-zero
302
 
        shell error code if not.  It's OK for this method to allow
303
 
        an exception to raise up.
304
 
        """
305
 
        raise NotImplementedError()
306
 
 
307
 
 
308
 
    def help(self):
309
 
        """Return help message for this class."""
310
 
        if self.__doc__ is Command.__doc__:
311
 
            return None
312
 
        return getdoc(self)
313
 
 
314
 
    def name(self):
315
 
        return _unsquish_command_name(self.__class__.__name__)
316
 
 
317
 
 
318
 
def parse_spec(spec):
319
 
    """
320
 
    >>> parse_spec(None)
321
 
    [None, None]
322
 
    >>> parse_spec("./")
323
 
    ['./', None]
324
 
    >>> parse_spec("../@")
325
 
    ['..', -1]
326
 
    >>> parse_spec("../f/@35")
327
 
    ['../f', 35]
328
 
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
329
 
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
330
 
    """
331
 
    if spec is None:
332
 
        return [None, None]
333
 
    if '/@' in spec:
334
 
        parsed = spec.split('/@')
335
 
        assert len(parsed) == 2
336
 
        if parsed[1] == "":
337
 
            parsed[1] = -1
338
 
        else:
339
 
            try:
340
 
                parsed[1] = int(parsed[1])
341
 
            except ValueError:
342
 
                pass # We can allow stuff like ./@revid:blahblahblah
343
 
            else:
344
 
                assert parsed[1] >=0
345
 
    else:
346
 
        parsed = [spec, None]
347
 
    return parsed
 
86
from bzrlib.store import ImmutableStore
 
87
from bzrlib.trace import mutter, note, log_error
 
88
from bzrlib.errors import bailout, BzrError
 
89
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
90
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
 
91
from bzrlib.revision import Revision
 
92
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
93
     format_date
 
94
 
 
95
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
 
96
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
 
97
 
 
98
## standard representation
 
99
NONE_STRING = '(none)'
 
100
EMPTY = 'empty'
 
101
 
 
102
 
 
103
## TODO: Perhaps a different version of inventory commands that
 
104
## returns iterators...
 
105
 
 
106
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
 
107
 
 
108
## TODO: Some kind of locking on branches.  Perhaps there should be a
 
109
## parameter to the branch object saying whether we want a read or
 
110
## write lock; release it from destructor.  Perhaps don't even need a
 
111
## read lock to look at immutable objects?
 
112
 
 
113
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
 
114
## to compare output?
 
115
 
 
116
## TODO: Some kind of global code to generate the right Branch object
 
117
## to work on.  Almost, but not quite all, commands need one, and it
 
118
## can be taken either from their parameters or their working
 
119
## directory.
 
120
 
 
121
## TODO: rename command, needed soon: check destination doesn't exist
 
122
## either in working copy or tree; move working copy; update
 
123
## inventory; write out
 
124
 
 
125
## TODO: move command; check destination is a directory and will not
 
126
## clash; move it.
 
127
 
 
128
## TODO: command to show renames, one per line, as to->from
 
129
 
 
130
 
 
131
 
 
132
 
 
133
def cmd_status(all=False):
 
134
    """Display status summary.
 
135
 
 
136
    For each file there is a single line giving its file state and name.
 
137
    The name is that in the current revision unless it is deleted or
 
138
    missing, in which case the old name is shown.
 
139
 
 
140
    :todo: Don't show unchanged files unless ``--all`` is given?
 
141
    """
 
142
    Branch('.').show_status(show_all=all)
 
143
 
 
144
 
 
145
 
 
146
######################################################################
 
147
# examining history
 
148
def cmd_get_revision(revision_id):
 
149
    Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
150
 
 
151
 
 
152
def cmd_get_file_text(text_id):
 
153
    """Get contents of a file by hash."""
 
154
    sf = Branch('.').text_store[text_id]
 
155
    pumpfile(sf, sys.stdout)
 
156
 
 
157
 
 
158
 
 
159
######################################################################
 
160
# commands
 
161
    
 
162
 
 
163
def cmd_revno():
 
164
    """Show number of revisions on this branch"""
 
165
    print Branch('.').revno()
 
166
    
 
167
 
 
168
    
 
169
def cmd_add(file_list, verbose=False):
 
170
    """Add specified files or directories.
 
171
 
 
172
    In non-recursive mode, all the named items are added, regardless
 
173
    of whether they were previously ignored.  A warning is given if
 
174
    any of the named files are already versioned.
 
175
 
 
176
    In recursive mode (the default), files are treated the same way
 
177
    but the behaviour for directories is different.  Directories that
 
178
    are already versioned do not give a warning.  All directories,
 
179
    whether already versioned or not, are searched for files or
 
180
    subdirectories that are neither versioned or ignored, and these
 
181
    are added.  This search proceeds recursively into versioned
 
182
    directories.
 
183
 
 
184
    Therefore simply saying 'bzr add .' will version all files that
 
185
    are currently unknown.
 
186
    """
 
187
    if True:
 
188
        bzrlib.add.smart_add(file_list, verbose)
 
189
    else:
 
190
        # old way
 
191
        assert file_list
 
192
        b = Branch(file_list[0], find_root=True)
 
193
        b.add([b.relpath(f) for f in file_list], verbose=verbose)
 
194
 
 
195
    
 
196
 
 
197
def cmd_relpath(filename):
 
198
    print Branch(filename).relpath(filename)
 
199
 
 
200
 
 
201
def cmd_inventory(revision=None):
 
202
    """Show inventory of the current working copy."""
 
203
    ## TODO: Also optionally show a previous inventory
 
204
    ## TODO: Format options
 
205
    b = Branch('.')
 
206
    if revision == None:
 
207
        inv = b.read_working_inventory()
 
208
    else:
 
209
        inv = b.get_revision_inventory(b.lookup_revision(revision))
 
210
        
 
211
    for path, entry in inv.iter_entries():
 
212
        print '%-50s %s' % (entry.file_id, path)
 
213
 
 
214
 
 
215
 
 
216
def cmd_info():
 
217
    b = Branch('.')
 
218
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
 
219
 
 
220
    def plural(n, base='', pl=None):
 
221
        if n == 1:
 
222
            return base
 
223
        elif pl is not None:
 
224
            return pl
 
225
        else:
 
226
            return 's'
 
227
 
 
228
    count_version_dirs = 0
 
229
 
 
230
    count_status = {'A': 0, 'D': 0, 'M': 0, 'R': 0, '?': 0, 'I': 0, '.': 0}
 
231
    for st_tup in bzrlib.diff_trees(b.basis_tree(), b.working_tree()):
 
232
        fs = st_tup[0]
 
233
        count_status[fs] += 1
 
234
        if fs not in ['I', '?'] and st_tup[4] == 'directory':
 
235
            count_version_dirs += 1
 
236
 
 
237
    print
 
238
    print 'in the working tree:'
 
239
    for name, fs in (('unchanged', '.'),
 
240
                     ('modified', 'M'), ('added', 'A'), ('removed', 'D'),
 
241
                     ('renamed', 'R'), ('unknown', '?'), ('ignored', 'I'),
 
242
                     ):
 
243
        print '  %5d %s' % (count_status[fs], name)
 
244
    print '  %5d versioned subdirector%s' % (count_version_dirs,
 
245
                                             plural(count_version_dirs, 'y', 'ies'))
 
246
 
 
247
    print
 
248
    print 'branch history:'
 
249
    history = b.revision_history()
 
250
    revno = len(history)
 
251
    print '  %5d revision%s' % (revno, plural(revno))
 
252
    committers = Set()
 
253
    for rev in history:
 
254
        committers.add(b.get_revision(rev).committer)
 
255
    print '  %5d committer%s' % (len(committers), plural(len(committers)))
 
256
    if revno > 0:
 
257
        firstrev = b.get_revision(history[0])
 
258
        age = int((time.time() - firstrev.timestamp) / 3600 / 24)
 
259
        print '  %5d day%s old' % (age, plural(age))
 
260
        print '  first revision: %s' % format_date(firstrev.timestamp,
 
261
                                                 firstrev.timezone)
 
262
 
 
263
        lastrev = b.get_revision(history[-1])
 
264
        print '  latest revision: %s' % format_date(lastrev.timestamp,
 
265
                                                    lastrev.timezone)
 
266
        
 
267
    
 
268
 
 
269
 
 
270
def cmd_remove(file_list, verbose=False):
 
271
    b = Branch(file_list[0])
 
272
    b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
273
 
 
274
 
 
275
 
 
276
def cmd_file_id(filename):
 
277
    b = Branch(filename)
 
278
    i = b.inventory.path2id(b.relpath(filename))
 
279
    if i is None:
 
280
        bailout("%s is not a versioned file" % filename)
 
281
    else:
 
282
        print i
 
283
 
 
284
 
 
285
def cmd_find_filename(fileid):
 
286
    n = find_filename(fileid)
 
287
    if n is None:
 
288
        bailout("%s is not a live file id" % fileid)
 
289
    else:
 
290
        print n
 
291
 
 
292
 
 
293
def cmd_revision_history():
 
294
    for patchid in Branch('.').revision_history():
 
295
        print patchid
 
296
 
 
297
 
 
298
 
 
299
def cmd_init():
 
300
    # TODO: Check we're not already in a working directory?  At the
 
301
    # moment you'll get an ugly error.
 
302
    
 
303
    # TODO: What if we're in a subdirectory of a branch?  Would like
 
304
    # to allow that, but then the parent may need to understand that
 
305
    # the children have disappeared, or should they be versioned in
 
306
    # both?
 
307
 
 
308
    # TODO: Take an argument/option for branch name.
 
309
    Branch('.', init=True)
 
310
 
 
311
 
 
312
def cmd_diff(revision=None):
 
313
    """Show diff from basis to working copy.
 
314
 
 
315
    :todo: Take one or two revision arguments, look up those trees,
 
316
           and diff them.
 
317
 
 
318
    :todo: Allow diff across branches.
 
319
 
 
320
    :todo: Mangle filenames in diff to be more relevant.
 
321
 
 
322
    :todo: Shouldn't be in the cmd function.
 
323
    """
 
324
 
 
325
    b = Branch('.')
 
326
 
 
327
    if revision == None:
 
328
        old_tree = b.basis_tree()
 
329
    else:
 
330
        old_tree = b.revision_tree(b.lookup_revision(revision))
 
331
        
 
332
    new_tree = b.working_tree()
 
333
    old_inv = old_tree.inventory
 
334
    new_inv = new_tree.inventory
 
335
 
 
336
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
337
    old_label = ''
 
338
    new_label = ''
 
339
 
 
340
    DEVNULL = '/dev/null'
 
341
    # Windows users, don't panic about this filename -- it is a
 
342
    # special signal to GNU patch that the file should be created or
 
343
    # deleted respectively.
 
344
 
 
345
    # TODO: Generation of pseudo-diffs for added/deleted files could
 
346
    # be usefully made into a much faster special case.
 
347
 
 
348
    # TODO: Better to return them in sorted order I think.
 
349
    
 
350
    for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
 
351
        d = None
 
352
 
 
353
        # Don't show this by default; maybe do it if an option is passed
 
354
        # idlabel = '      {%s}' % fid
 
355
        idlabel = ''
 
356
 
 
357
        # FIXME: Something about the diff format makes patch unhappy
 
358
        # with newly-added files.
 
359
 
 
360
        def diffit(*a, **kw):
 
361
            sys.stdout.writelines(difflib.unified_diff(*a, **kw))
 
362
            print
 
363
        
 
364
        if file_state in ['.', '?', 'I']:
 
365
            continue
 
366
        elif file_state == 'A':
 
367
            print '*** added %s %r' % (kind, new_name)
 
368
            if kind == 'file':
 
369
                diffit([],
 
370
                       new_tree.get_file(fid).readlines(),
 
371
                       fromfile=DEVNULL,
 
372
                       tofile=new_label + new_name + idlabel)
 
373
        elif file_state == 'D':
 
374
            assert isinstance(old_name, types.StringTypes)
 
375
            print '*** deleted %s %r' % (kind, old_name)
 
376
            if kind == 'file':
 
377
                diffit(old_tree.get_file(fid).readlines(), [],
 
378
                       fromfile=old_label + old_name + idlabel,
 
379
                       tofile=DEVNULL)
 
380
        elif file_state in ['M', 'R']:
 
381
            if file_state == 'M':
 
382
                assert kind == 'file'
 
383
                assert old_name == new_name
 
384
                print '*** modified %s %r' % (kind, new_name)
 
385
            elif file_state == 'R':
 
386
                print '*** renamed %s %r => %r' % (kind, old_name, new_name)
 
387
 
 
388
            if kind == 'file':
 
389
                diffit(old_tree.get_file(fid).readlines(),
 
390
                       new_tree.get_file(fid).readlines(),
 
391
                       fromfile=old_label + old_name + idlabel,
 
392
                       tofile=new_label + new_name)
 
393
        else:
 
394
            bailout("can't represent state %s {%s}" % (file_state, fid))
 
395
 
 
396
 
 
397
 
 
398
def cmd_root(filename=None):
 
399
    """Print the branch root."""
 
400
    print bzrlib.branch.find_branch_root(filename)
 
401
    
 
402
 
 
403
def cmd_log(timezone='original'):
 
404
    """Show log of this branch.
 
405
 
 
406
    :todo: Options for utc; to show ids; to limit range; etc.
 
407
    """
 
408
    Branch('.').write_log(show_timezone=timezone)
 
409
 
 
410
 
 
411
def cmd_ls(revision=None, verbose=False):
 
412
    """List files in a tree.
 
413
 
 
414
    :todo: Take a revision or remote path and list that tree instead.
 
415
    """
 
416
    b = Branch('.')
 
417
    if revision == None:
 
418
        tree = b.working_tree()
 
419
    else:
 
420
        tree = b.revision_tree(b.lookup_revision(revision))
 
421
        
 
422
    for fp, fc, kind, fid in tree.list_files():
 
423
        if verbose:
 
424
            if kind == 'directory':
 
425
                kindch = '/'
 
426
            elif kind == 'file':
 
427
                kindch = ''
 
428
            else:
 
429
                kindch = '???'
 
430
                
 
431
            print '%-8s %s%s' % (fc, fp, kindch)
 
432
        else:
 
433
            print fp
 
434
    
 
435
    
 
436
 
 
437
def cmd_unknowns():
 
438
    """List unknown files"""
 
439
    for f in Branch('.').unknowns():
 
440
        print quotefn(f)
 
441
 
 
442
 
 
443
def cmd_lookup_revision(revno):
 
444
    try:
 
445
        revno = int(revno)
 
446
    except ValueError:
 
447
        bailout("usage: lookup-revision REVNO",
 
448
                ["REVNO is a non-negative revision number for this branch"])
 
449
 
 
450
    print Branch('.').lookup_revision(revno) or NONE_STRING
 
451
 
 
452
 
 
453
 
 
454
def cmd_export(revno, dest):
 
455
    """Export past revision to destination directory."""
 
456
    b = Branch('.')
 
457
    rh = b.lookup_revision(int(revno))
 
458
    t = b.revision_tree(rh)
 
459
    t.export(dest)
 
460
 
 
461
 
 
462
 
 
463
######################################################################
 
464
# internal/test commands
 
465
 
 
466
 
 
467
def cmd_uuid():
 
468
    """Print a newly-generated UUID."""
 
469
    print bzrlib.osutils.uuid()
 
470
 
 
471
 
 
472
 
 
473
def cmd_local_time_offset():
 
474
    print bzrlib.osutils.local_time_offset()
 
475
 
 
476
 
 
477
 
 
478
def cmd_commit(message=None, verbose=False):
 
479
    if not message:
 
480
        bailout("please specify a commit message")
 
481
    Branch('.').commit(message, verbose=verbose)
 
482
 
 
483
 
 
484
def cmd_check():
 
485
    """Check consistency of the branch."""
 
486
    check()
 
487
 
 
488
 
 
489
def cmd_is(pred, *rest):
 
490
    """Test whether PREDICATE is true."""
 
491
    try:
 
492
        cmd_handler = globals()['assert_' + pred.replace('-', '_')]
 
493
    except KeyError:
 
494
        bailout("unknown predicate: %s" % quotefn(pred))
 
495
        
 
496
    try:
 
497
        cmd_handler(*rest)
 
498
    except BzrCheckError:
 
499
        # by default we don't print the message so that this can
 
500
        # be used from shell scripts without producing noise
 
501
        sys.exit(1)
 
502
 
 
503
 
 
504
def cmd_username():
 
505
    print bzrlib.osutils.username()
 
506
 
 
507
 
 
508
def cmd_user_email():
 
509
    print bzrlib.osutils.user_email()
 
510
 
 
511
 
 
512
def cmd_gen_revision_id():
 
513
    import time
 
514
    print bzrlib.branch._gen_revision_id(time.time())
 
515
 
 
516
 
 
517
def cmd_selftest(verbose=False):
 
518
    """Run internal test suite"""
 
519
    ## -v, if present, is seen by doctest; the argument is just here
 
520
    ## so our parser doesn't complain
 
521
 
 
522
    ## TODO: --verbose option
 
523
 
 
524
    failures, tests = 0, 0
 
525
    
 
526
    import doctest, bzrlib.store, bzrlib.tests
 
527
    bzrlib.trace.verbose = False
 
528
 
 
529
    for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
530
        bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
531
        mf, mt = doctest.testmod(m)
 
532
        failures += mf
 
533
        tests += mt
 
534
        print '%-40s %3d tests' % (m.__name__, mt),
 
535
        if mf:
 
536
            print '%3d FAILED!' % mf
 
537
        else:
 
538
            print
 
539
 
 
540
    print '%-40s %3d tests' % ('total', tests),
 
541
    if failures:
 
542
        print '%3d FAILED!' % failures
 
543
    else:
 
544
        print
 
545
 
 
546
 
 
547
 
 
548
# deprecated
 
549
cmd_doctest = cmd_selftest
 
550
 
 
551
 
 
552
######################################################################
 
553
# help
 
554
 
 
555
 
 
556
def cmd_help():
 
557
    # TODO: Specific help for particular commands
 
558
    print __doc__
 
559
 
 
560
 
 
561
def cmd_version():
 
562
    print "bzr (bazaar-ng) %s" % __version__
 
563
    print __copyright__
 
564
    print "http://bazaar-ng.org/"
 
565
    print
 
566
    print \
 
567
"""bzr comes with ABSOLUTELY NO WARRANTY.  bzr is free software, and
 
568
you may use, modify and redistribute it under the terms of the GNU 
 
569
General Public License version 2 or later."""
 
570
 
 
571
 
 
572
def cmd_rocks():
 
573
    """Statement of optimism."""
 
574
    print "it sure does!"
 
575
 
 
576
 
 
577
 
 
578
######################################################################
 
579
# main routine
348
580
 
349
581
 
350
582
# list of all available options; the rhs can be either None for an
352
584
# the type.
353
585
OPTIONS = {
354
586
    'all':                    None,
355
 
    'diff-options':           str,
356
587
    'help':                   None,
357
 
    'file':                   unicode,
358
 
    'force':                  None,
359
 
    'format':                 unicode,
360
 
    'forward':                None,
361
588
    'message':                unicode,
362
 
    'no-recurse':             None,
363
 
    'profile':                None,
364
 
    'revision':               _parse_revision_str,
365
 
    'short':                  None,
 
589
    'revision':               int,
366
590
    'show-ids':               None,
367
591
    'timezone':               str,
368
592
    'verbose':                None,
369
593
    'version':                None,
370
 
    'email':                  None,
371
 
    'unchanged':              None,
372
 
    'update':                 None,
373
 
    'long':                   None,
374
 
    'root':                   str,
375
 
    'no-backup':              None,
376
 
    'merge-type':             get_merge_type,
377
 
    'pattern':                str,
378
594
    }
379
595
 
380
596
SHORT_OPTIONS = {
381
 
    'F':                      'file', 
382
 
    'h':                      'help',
383
597
    'm':                      'message',
384
598
    'r':                      'revision',
385
599
    'v':                      'verbose',
386
 
    'l':                      'long',
387
600
}
388
601
 
 
602
# List of options that apply to particular commands; commands not
 
603
# listed take none.
 
604
cmd_options = {
 
605
    'add':                    ['verbose'],
 
606
    'commit':                 ['message', 'verbose'],
 
607
    'diff':                   ['revision'],
 
608
    'inventory':              ['revision'],
 
609
    'log':                    ['show-ids', 'timezone'],
 
610
    'ls':                     ['revision', 'verbose'],
 
611
    'remove':                 ['verbose'],
 
612
    'status':                 ['all'],
 
613
    }
 
614
 
 
615
 
 
616
cmd_args = {
 
617
    'init':                   [],
 
618
    'add':                    ['file+'],
 
619
    'commit':                 [],
 
620
    'diff':                   [],
 
621
    'file-id':                ['filename'],
 
622
    'root':                   ['filename?'],
 
623
    'relpath':                ['filename'],
 
624
    'get-file-text':          ['text_id'],
 
625
    'get-inventory':          ['inventory_id'],
 
626
    'get-revision':           ['revision_id'],
 
627
    'get-revision-inventory': ['revision_id'],
 
628
    'log':                    [],
 
629
    'lookup-revision':        ['revno'],
 
630
    'export':                 ['revno', 'dest'],
 
631
    'remove':                 ['file+'],
 
632
    'status':                 [],
 
633
    }
 
634
 
389
635
 
390
636
def parse_args(argv):
391
637
    """Parse command line.
397
643
 
398
644
    >>> parse_args('--help'.split())
399
645
    ([], {'help': True})
400
 
    >>> parse_args('help -- --invalidcmd'.split())
401
 
    (['help', '--invalidcmd'], {})
402
646
    >>> parse_args('--version'.split())
403
647
    ([], {'version': True})
404
648
    >>> parse_args('status --all'.split())
405
649
    (['status'], {'all': True})
406
650
    >>> parse_args('commit --message=biter'.split())
407
651
    (['commit'], {'message': u'biter'})
408
 
    >>> parse_args('log -r 500'.split())
409
 
    (['log'], {'revision': [<RevisionSpec_int 500>]})
410
 
    >>> parse_args('log -r500..600'.split())
411
 
    (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
412
 
    >>> parse_args('log -vr500..600'.split())
413
 
    (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
414
 
    >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
415
 
    (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
416
652
    """
417
653
    args = []
418
654
    opts = {}
419
655
 
420
 
    argsover = False
 
656
    # TODO: Maybe handle '--' to end options?
 
657
 
421
658
    while argv:
422
659
        a = argv.pop(0)
423
 
        if not argsover and a[0] == '-':
424
 
            # option names must not be unicode
425
 
            a = str(a)
 
660
        if a[0] == '-':
426
661
            optarg = None
427
662
            if a[1] == '-':
428
 
                if a == '--':
429
 
                    # We've received a standalone -- No more flags
430
 
                    argsover = True
431
 
                    continue
432
663
                mutter("  got option %r" % a)
433
664
                if '=' in a:
434
665
                    optname, optarg = a[2:].split('=', 1)
435
666
                else:
436
667
                    optname = a[2:]
437
668
                if optname not in OPTIONS:
438
 
                    raise BzrError('unknown long option %r' % a)
 
669
                    bailout('unknown long option %r' % a)
439
670
            else:
440
671
                shortopt = a[1:]
441
 
                if shortopt in SHORT_OPTIONS:
442
 
                    # Multi-character options must have a space to delimit
443
 
                    # their value
444
 
                    optname = SHORT_OPTIONS[shortopt]
445
 
                else:
446
 
                    # Single character short options, can be chained,
447
 
                    # and have their value appended to their name
448
 
                    shortopt = a[1:2]
449
 
                    if shortopt not in SHORT_OPTIONS:
450
 
                        # We didn't find the multi-character name, and we
451
 
                        # didn't find the single char name
452
 
                        raise BzrError('unknown short option %r' % a)
453
 
                    optname = SHORT_OPTIONS[shortopt]
454
 
 
455
 
                    if a[2:]:
456
 
                        # There are extra things on this option
457
 
                        # see if it is the value, or if it is another
458
 
                        # short option
459
 
                        optargfn = OPTIONS[optname]
460
 
                        if optargfn is None:
461
 
                            # This option does not take an argument, so the
462
 
                            # next entry is another short option, pack it back
463
 
                            # into the list
464
 
                            argv.insert(0, '-' + a[2:])
465
 
                        else:
466
 
                            # This option takes an argument, so pack it
467
 
                            # into the array
468
 
                            optarg = a[2:]
 
672
                if shortopt not in SHORT_OPTIONS:
 
673
                    bailout('unknown short option %r' % a)
 
674
                optname = SHORT_OPTIONS[shortopt]
469
675
            
470
676
            if optname in opts:
471
677
                # XXX: Do we ever want to support this, e.g. for -r?
472
 
                raise BzrError('repeated option %r' % a)
 
678
                bailout('repeated option %r' % a)
473
679
                
474
680
            optargfn = OPTIONS[optname]
475
681
            if optargfn:
476
682
                if optarg == None:
477
683
                    if not argv:
478
 
                        raise BzrError('option %r needs an argument' % a)
 
684
                        bailout('option %r needs an argument' % a)
479
685
                    else:
480
686
                        optarg = argv.pop(0)
481
687
                opts[optname] = optargfn(optarg)
 
688
                mutter("    option argument %r" % opts[optname])
482
689
            else:
483
690
                if optarg != None:
484
 
                    raise BzrError('option %r takes no argument' % optname)
 
691
                    bailout('option %r takes no argument' % optname)
485
692
                opts[optname] = True
486
693
        else:
487
694
            args.append(a)
490
697
 
491
698
 
492
699
 
 
700
def _match_args(cmd, args):
 
701
    """Check non-option arguments match required pattern.
493
702
 
494
 
def _match_argform(cmd, takes_args, args):
 
703
    >>> _match_args('status', ['asdasdsadasd'])
 
704
    Traceback (most recent call last):
 
705
    ...
 
706
    BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
 
707
    >>> _match_args('add', ['asdasdsadasd'])
 
708
    {'file_list': ['asdasdsadasd']}
 
709
    >>> _match_args('add', 'abc def gj'.split())
 
710
    {'file_list': ['abc', 'def', 'gj']}
 
711
    """
 
712
    # match argument pattern
 
713
    argform = cmd_args.get(cmd, [])
495
714
    argdict = {}
 
715
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
 
716
    # all but one.
496
717
 
497
 
    # step through args and takes_args, allowing appropriate 0-many matches
498
 
    for ap in takes_args:
 
718
    # step through args and argform, allowing appropriate 0-many matches
 
719
    for ap in argform:
499
720
        argname = ap[:-1]
500
721
        if ap[-1] == '?':
501
722
            if args:
502
723
                argdict[argname] = args.pop(0)
503
 
        elif ap[-1] == '*': # all remaining arguments
504
 
            if args:
505
 
                argdict[argname + '_list'] = args[:]
506
 
                args = []
507
 
            else:
508
 
                argdict[argname + '_list'] = None
 
724
        elif ap[-1] == '*':
 
725
            assert 0
509
726
        elif ap[-1] == '+':
510
727
            if not args:
511
 
                raise BzrCommandError("command %r needs one or more %s"
 
728
                bailout("command %r needs one or more %s"
512
729
                        % (cmd, argname.upper()))
513
730
            else:
514
731
                argdict[argname + '_list'] = args[:]
515
732
                args = []
516
 
        elif ap[-1] == '$': # all but one
517
 
            if len(args) < 2:
518
 
                raise BzrCommandError("command %r needs one or more %s"
519
 
                        % (cmd, argname.upper()))
520
 
            argdict[argname + '_list'] = args[:-1]
521
 
            args[:-1] = []                
522
733
        else:
523
734
            # just a plain arg
524
735
            argname = ap
525
736
            if not args:
526
 
                raise BzrCommandError("command %r requires argument %s"
 
737
                bailout("command %r requires argument %s"
527
738
                        % (cmd, argname.upper()))
528
739
            else:
529
740
                argdict[argname] = args.pop(0)
530
741
            
531
742
    if args:
532
 
        raise BzrCommandError("extra argument to command %s: %s"
533
 
                              % (cmd, args[0]))
 
743
        bailout("extra arguments to command %s: %r"
 
744
                % (cmd, args))
534
745
 
535
746
    return argdict
536
747
 
537
748
 
538
749
 
539
 
def apply_profiled(the_callable, *args, **kwargs):
540
 
    import hotshot
541
 
    import tempfile
542
 
    pffileno, pfname = tempfile.mkstemp()
543
 
    try:
544
 
        prof = hotshot.Profile(pfname)
545
 
        try:
546
 
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
547
 
        finally:
548
 
            prof.close()
549
 
 
550
 
        import hotshot.stats
551
 
        stats = hotshot.stats.load(pfname)
552
 
        #stats.strip_dirs()
553
 
        stats.sort_stats('time')
554
 
        ## XXX: Might like to write to stderr or the trace file instead but
555
 
        ## print_stats seems hardcoded to stdout
556
 
        stats.print_stats(20)
557
 
 
558
 
        return ret
559
 
    finally:
560
 
        os.close(pffileno)
561
 
        os.remove(pfname)
562
 
 
563
 
 
564
750
def run_bzr(argv):
565
751
    """Execute a command.
566
752
 
567
753
    This is similar to main(), but without all the trappings for
568
 
    logging and error handling.  
569
 
    
570
 
    argv
571
 
       The command-line arguments, without the program name from argv[0]
572
 
    
573
 
    Returns a command status or raises an exception.
574
 
 
575
 
    Special master options: these must come before the command because
576
 
    they control how the command is interpreted.
577
 
 
578
 
    --no-plugins
579
 
        Do not load plugin modules at all
580
 
 
581
 
    --builtin
582
 
        Only use builtin commands.  (Plugins are still allowed to change
583
 
        other behaviour.)
584
 
 
585
 
    --profile
586
 
        Run under the Python profiler.
 
754
    logging and error handling.
587
755
    """
588
 
    
589
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
590
 
 
591
 
    opt_profile = opt_no_plugins = opt_builtin = False
592
 
 
593
 
    # --no-plugins is handled specially at a very early stage. We need
594
 
    # to load plugins before doing other command parsing so that they
595
 
    # can override commands, but this needs to happen first.
596
 
 
597
 
    for a in argv:
598
 
        if a == '--profile':
599
 
            opt_profile = True
600
 
        elif a == '--no-plugins':
601
 
            opt_no_plugins = True
602
 
        elif a == '--builtin':
603
 
            opt_builtin = True
604
 
        else:
605
 
            break
606
 
        argv.remove(a)
607
 
 
608
 
    if (not argv) or (argv[0] == '--help'):
609
 
        from bzrlib.help import help
610
 
        if len(argv) > 1:
611
 
            help(argv[1])
612
 
        else:
613
 
            help()
614
 
        return 0
615
 
 
616
 
    if argv[0] == '--version':
617
 
        from bzrlib.builtins import show_version
618
 
        show_version()
619
 
        return 0
620
 
        
621
 
    if not opt_no_plugins:
622
 
        from bzrlib.plugin import load_plugins
623
 
        load_plugins()
624
 
 
625
 
    cmd = str(argv.pop(0))
626
 
 
627
 
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
628
 
 
629
 
    if opt_profile:
630
 
        ret = apply_profiled(cmd_obj.run_argv, argv)
631
 
    else:
632
 
        ret = cmd_obj.run_argv(argv)
633
 
    return ret or 0
 
756
    try:
 
757
        args, opts = parse_args(argv[1:])
 
758
        if 'help' in opts:
 
759
            # TODO: pass down other arguments in case they asked for
 
760
            # help on a command name?
 
761
            cmd_help()
 
762
            return 0
 
763
        elif 'version' in opts:
 
764
            cmd_version()
 
765
            return 0
 
766
        cmd = args.pop(0)
 
767
    except IndexError:
 
768
        log_error('usage: bzr COMMAND\n')
 
769
        log_error('  try "bzr help"\n')
 
770
        return 1
 
771
            
 
772
    try:
 
773
        cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
 
774
    except KeyError:
 
775
        bailout("unknown command " + `cmd`)
 
776
 
 
777
    # TODO: special --profile option to turn on the Python profiler
 
778
 
 
779
    # check options are reasonable
 
780
    allowed = cmd_options.get(cmd, [])
 
781
    for oname in opts:
 
782
        if oname not in allowed:
 
783
            bailout("option %r is not allowed for command %r"
 
784
                    % (oname, cmd))
 
785
 
 
786
    cmdargs = _match_args(cmd, args)
 
787
    cmdargs.update(opts)
 
788
 
 
789
    ret = cmd_handler(**cmdargs) or 0
 
790
 
634
791
 
635
792
 
636
793
def main(argv):
637
 
    import bzrlib.ui
638
 
    bzrlib.trace.log_startup(argv)
639
 
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
640
 
 
641
 
    return run_bzr_catch_errors(argv[1:])
642
 
 
643
 
 
644
 
def run_bzr_catch_errors(argv):
 
794
    ## TODO: Handle command-line options; probably know what options are valid for
 
795
    ## each command
 
796
 
 
797
    ## TODO: If the arguments are wrong, give a usage message rather
 
798
    ## than just a backtrace.
 
799
 
 
800
    bzrlib.trace.create_tracefile(argv)
 
801
    
645
802
    try:
646
 
        try:
647
 
            try:
648
 
                return run_bzr(argv)
649
 
            finally:
650
 
                # do this here inside the exception wrappers to catch EPIPE
651
 
                sys.stdout.flush()
652
 
        #wrap common errors as CommandErrors.
653
 
        except (NotBranchError,), e:
654
 
            raise BzrCommandError(str(e))
655
 
    except BzrCommandError, e:
656
 
        # command line syntax error, etc
657
 
        log_error(str(e))
658
 
        return 1
 
803
        ret = run_bzr(argv)
 
804
        return ret
659
805
    except BzrError, e:
660
 
        bzrlib.trace.log_exception()
 
806
        log_error('bzr: error: ' + e.args[0] + '\n')
 
807
        if len(e.args) > 1:
 
808
            for h in e.args[1]:
 
809
                log_error('  ' + h + '\n')
661
810
        return 1
662
 
    except AssertionError, e:
663
 
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
664
 
        return 3
665
 
    except KeyboardInterrupt, e:
666
 
        bzrlib.trace.note('interrupted')
667
 
        return 2
668
811
    except Exception, e:
669
 
        import errno
670
 
        if (isinstance(e, IOError) 
671
 
            and hasattr(e, 'errno')
672
 
            and e.errno == errno.EPIPE):
673
 
            bzrlib.trace.note('broken pipe')
674
 
            return 2
675
 
        else:
676
 
            bzrlib.trace.log_exception()
677
 
            return 2
 
812
        log_error('bzr: exception: %s\n' % e)
 
813
        log_error('    see .bzr.log for details\n')
 
814
        traceback.print_exc(None, bzrlib.trace._tracefile)
 
815
        traceback.print_exc(None, sys.stderr)
 
816
        return 1
 
817
 
 
818
    # TODO: Maybe nicer handling of IOError?
 
819
 
678
820
 
679
821
 
680
822
if __name__ == '__main__':
681
823
    sys.exit(main(sys.argv))
 
824
    ##import profile
 
825
    ##profile.run('main(sys.argv)')
 
826