~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 06:25:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050323062555-5489339018d0c043
- import a subset of elementtree for easier installation

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
 
# 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
 
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
34
84
 
35
85
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.
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
 
    """
117
 
    import re
118
 
    old_format_re = re.compile('\d*:\d*')
119
 
    m = old_format_re.match(revstr)
120
 
    revs = []
121
 
    if m:
122
 
        warning('Colon separator for revision numbers is deprecated.'
123
 
                ' Use .. instead')
124
 
        for rev in revstr.split(':'):
125
 
            if rev:
126
 
                revs.append(RevisionSpec(int(rev)))
127
 
            else:
128
 
                revs.append(RevisionSpec(None))
129
 
    else:
130
 
        for x in revstr.split('..'):
131
 
            if not x:
132
 
                revs.append(RevisionSpec(None))
133
 
            else:
134
 
                revs.append(RevisionSpec(x))
135
 
    return revs
136
 
 
137
 
 
138
 
def _builtin_commands():
139
 
    import bzrlib.builtins
140
 
    r = {}
141
 
    builtins = bzrlib.builtins.__dict__
142
 
    for name in builtins:
143
 
        if name.startswith("cmd_"):
144
 
            real_name = _unsquish_command_name(name)        
145
 
            r[real_name] = builtins[name]
146
 
    return r
147
 
 
148
 
            
149
 
 
150
 
def builtin_command_names():
151
 
    """Return list of builtin command names."""
152
 
    return _builtin_commands().keys()
153
 
    
154
 
 
155
 
def plugin_command_names():
156
 
    return plugin_cmds.keys()
157
 
 
158
 
 
159
 
def _get_cmd_dict(plugins_override=True):
160
 
    """Return name->class mapping for all commands."""
161
 
    d = _builtin_commands()
162
 
    if plugins_override:
163
 
        d.update(plugin_cmds)
164
 
    return d
165
 
 
166
 
    
167
 
def get_all_cmds(plugins_override=True):
168
 
    """Return canonical name and class for all registered commands."""
169
 
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
170
 
        yield k,v
171
 
 
172
 
 
173
 
def get_cmd_object(cmd_name, plugins_override=True):
174
 
    """Return the canonical name and command class for a command.
175
 
 
176
 
    plugins_override
177
 
        If true, plugin commands can override builtins.
178
 
    """
179
 
    from bzrlib.externalcommand import ExternalCommand
180
 
 
181
 
    cmd_name = str(cmd_name)            # not unicode
182
 
 
183
 
    # first look up this command under the specified name
184
 
    cmds = _get_cmd_dict(plugins_override=plugins_override)
185
 
    try:
186
 
        return cmds[cmd_name]()
 
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('-', '_')]
187
493
    except KeyError:
188
 
        pass
189
 
 
190
 
    # look for any command which claims this as an alias
191
 
    for real_cmd_name, cmd_class in cmds.iteritems():
192
 
        if cmd_name in cmd_class.aliases:
193
 
            return cmd_class()
194
 
 
195
 
    cmd_obj = ExternalCommand.find_command(cmd_name)
196
 
    if cmd_obj:
197
 
        return cmd_obj
198
 
 
199
 
    raise BzrCommandError("unknown command %r" % cmd_name)
200
 
 
201
 
 
202
 
class Command(object):
203
 
    """Base class for commands.
204
 
 
205
 
    Commands are the heart of the command-line bzr interface.
206
 
 
207
 
    The command object mostly handles the mapping of command-line
208
 
    parameters into one or more bzrlib operations, and of the results
209
 
    into textual output.
210
 
 
211
 
    Commands normally don't have any state.  All their arguments are
212
 
    passed in to the run method.  (Subclasses may take a different
213
 
    policy if the behaviour of the instance needs to depend on e.g. a
214
 
    shell plugin and not just its Python class.)
215
 
 
216
 
    The docstring for an actual command should give a single-line
217
 
    summary, then a complete description of the command.  A grammar
218
 
    description will be inserted.
219
 
 
220
 
    aliases
221
 
        Other accepted names for this command.
222
 
 
223
 
    takes_args
224
 
        List of argument forms, marked with whether they are optional,
225
 
        repeated, etc.
226
 
 
227
 
    takes_options
228
 
        List of options that may be given for this command.
229
 
 
230
 
    hidden
231
 
        If true, this command isn't advertised.  This is typically
232
 
        for commands intended for expert users.
233
 
    """
234
 
    aliases = []
235
 
    
236
 
    takes_args = []
237
 
    takes_options = []
238
 
 
239
 
    hidden = False
240
 
    
241
 
    def __init__(self):
242
 
        """Construct an instance of this command."""
243
 
        if self.__doc__ == Command.__doc__:
244
 
            warn("No help message set for %r" % self)
245
 
 
246
 
 
247
 
    def run_argv(self, argv):
248
 
        """Parse command line and run."""
249
 
        args, opts = parse_args(argv)
250
 
 
251
 
        if 'help' in opts:  # e.g. bzr add --help
252
 
            from bzrlib.help import help_on_command
253
 
            help_on_command(self.name())
254
 
            return 0
255
 
 
256
 
        # check options are reasonable
257
 
        allowed = self.takes_options
258
 
        for oname in opts:
259
 
            if oname not in allowed:
260
 
                raise BzrCommandError("option '--%s' is not allowed for command %r"
261
 
                                      % (oname, self.name()))
262
 
 
263
 
        # mix arguments and options into one dictionary
264
 
        cmdargs = _match_argform(self.name(), self.takes_args, args)
265
 
        cmdopts = {}
266
 
        for k, v in opts.items():
267
 
            cmdopts[k.replace('-', '_')] = v
268
 
 
269
 
        all_cmd_args = cmdargs.copy()
270
 
        all_cmd_args.update(cmdopts)
271
 
 
272
 
        return self.run(**all_cmd_args)
273
 
 
274
 
    
275
 
    def run(self):
276
 
        """Actually run the command.
277
 
 
278
 
        This is invoked with the options and arguments bound to
279
 
        keyword parameters.
280
 
 
281
 
        Return 0 or None if the command was successful, or a non-zero
282
 
        shell error code if not.  It's OK for this method to allow
283
 
        an exception to raise up.
284
 
        """
285
 
        raise NotImplementedError()
286
 
 
287
 
 
288
 
    def help(self):
289
 
        """Return help message for this class."""
290
 
        if self.__doc__ is Command.__doc__:
291
 
            return None
292
 
        return getdoc(self)
293
 
 
294
 
    def name(self):
295
 
        return _unsquish_command_name(self.__class__.__name__)
296
 
 
297
 
 
298
 
def parse_spec(spec):
299
 
    """
300
 
    >>> parse_spec(None)
301
 
    [None, None]
302
 
    >>> parse_spec("./")
303
 
    ['./', None]
304
 
    >>> parse_spec("../@")
305
 
    ['..', -1]
306
 
    >>> parse_spec("../f/@35")
307
 
    ['../f', 35]
308
 
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
309
 
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
310
 
    """
311
 
    if spec is None:
312
 
        return [None, None]
313
 
    if '/@' in spec:
314
 
        parsed = spec.split('/@')
315
 
        assert len(parsed) == 2
316
 
        if parsed[1] == "":
317
 
            parsed[1] = -1
 
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
318
537
        else:
319
 
            try:
320
 
                parsed[1] = int(parsed[1])
321
 
            except ValueError:
322
 
                pass # We can allow stuff like ./@revid:blahblahblah
323
 
            else:
324
 
                assert parsed[1] >=0
 
538
            print
 
539
 
 
540
    print '%-40s %3d tests' % ('total', tests),
 
541
    if failures:
 
542
        print '%3d FAILED!' % failures
325
543
    else:
326
 
        parsed = [spec, None]
327
 
    return parsed
 
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
328
580
 
329
581
 
330
582
# list of all available options; the rhs can be either None for an
332
584
# the type.
333
585
OPTIONS = {
334
586
    'all':                    None,
335
 
    'basis':                  str,
336
 
    'diff-options':           str,
337
587
    'help':                   None,
338
 
    'file':                   unicode,
339
 
    'force':                  None,
340
 
    'format':                 unicode,
341
 
    'forward':                None,
342
588
    'message':                unicode,
343
 
    'no-recurse':             None,
344
 
    'profile':                None,
345
 
    'revision':               _parse_revision_str,
346
 
    'short':                  None,
 
589
    'revision':               int,
347
590
    'show-ids':               None,
348
591
    'timezone':               str,
349
592
    'verbose':                None,
350
593
    'version':                None,
351
 
    'email':                  None,
352
 
    'unchanged':              None,
353
 
    'update':                 None,
354
 
    'long':                   None,
355
 
    'root':                   str,
356
 
    'no-backup':              None,
357
 
    'pattern':                str,
358
594
    }
359
595
 
360
596
SHORT_OPTIONS = {
361
 
    'F':                      'file', 
362
 
    'h':                      'help',
363
597
    'm':                      'message',
364
598
    'r':                      'revision',
365
599
    'v':                      'verbose',
366
 
    'l':                      'long',
367
600
}
368
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
 
369
635
 
370
636
def parse_args(argv):
371
637
    """Parse command line.
377
643
 
378
644
    >>> parse_args('--help'.split())
379
645
    ([], {'help': True})
380
 
    >>> parse_args('help -- --invalidcmd'.split())
381
 
    (['help', '--invalidcmd'], {})
382
646
    >>> parse_args('--version'.split())
383
647
    ([], {'version': True})
384
648
    >>> parse_args('status --all'.split())
385
649
    (['status'], {'all': True})
386
650
    >>> parse_args('commit --message=biter'.split())
387
651
    (['commit'], {'message': u'biter'})
388
 
    >>> parse_args('log -r 500'.split())
389
 
    (['log'], {'revision': [<RevisionSpec_int 500>]})
390
 
    >>> parse_args('log -r500..600'.split())
391
 
    (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
392
 
    >>> parse_args('log -vr500..600'.split())
393
 
    (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
394
 
    >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
395
 
    (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
396
652
    """
397
653
    args = []
398
654
    opts = {}
399
655
 
400
 
    argsover = False
 
656
    # TODO: Maybe handle '--' to end options?
 
657
 
401
658
    while argv:
402
659
        a = argv.pop(0)
403
 
        if not argsover and a[0] == '-':
404
 
            # option names must not be unicode
405
 
            a = str(a)
 
660
        if a[0] == '-':
406
661
            optarg = None
407
662
            if a[1] == '-':
408
 
                if a == '--':
409
 
                    # We've received a standalone -- No more flags
410
 
                    argsover = True
411
 
                    continue
412
663
                mutter("  got option %r" % a)
413
664
                if '=' in a:
414
665
                    optname, optarg = a[2:].split('=', 1)
415
666
                else:
416
667
                    optname = a[2:]
417
668
                if optname not in OPTIONS:
418
 
                    raise BzrError('unknown long option %r' % a)
 
669
                    bailout('unknown long option %r' % a)
419
670
            else:
420
671
                shortopt = a[1:]
421
 
                if shortopt in SHORT_OPTIONS:
422
 
                    # Multi-character options must have a space to delimit
423
 
                    # their value
424
 
                    optname = SHORT_OPTIONS[shortopt]
425
 
                else:
426
 
                    # Single character short options, can be chained,
427
 
                    # and have their value appended to their name
428
 
                    shortopt = a[1:2]
429
 
                    if shortopt not in SHORT_OPTIONS:
430
 
                        # We didn't find the multi-character name, and we
431
 
                        # didn't find the single char name
432
 
                        raise BzrError('unknown short option %r' % a)
433
 
                    optname = SHORT_OPTIONS[shortopt]
434
 
 
435
 
                    if a[2:]:
436
 
                        # There are extra things on this option
437
 
                        # see if it is the value, or if it is another
438
 
                        # short option
439
 
                        optargfn = OPTIONS[optname]
440
 
                        if optargfn is None:
441
 
                            # This option does not take an argument, so the
442
 
                            # next entry is another short option, pack it back
443
 
                            # into the list
444
 
                            argv.insert(0, '-' + a[2:])
445
 
                        else:
446
 
                            # This option takes an argument, so pack it
447
 
                            # into the array
448
 
                            optarg = a[2:]
 
672
                if shortopt not in SHORT_OPTIONS:
 
673
                    bailout('unknown short option %r' % a)
 
674
                optname = SHORT_OPTIONS[shortopt]
449
675
            
450
676
            if optname in opts:
451
677
                # XXX: Do we ever want to support this, e.g. for -r?
452
 
                raise BzrError('repeated option %r' % a)
 
678
                bailout('repeated option %r' % a)
453
679
                
454
680
            optargfn = OPTIONS[optname]
455
681
            if optargfn:
456
682
                if optarg == None:
457
683
                    if not argv:
458
 
                        raise BzrError('option %r needs an argument' % a)
 
684
                        bailout('option %r needs an argument' % a)
459
685
                    else:
460
686
                        optarg = argv.pop(0)
461
687
                opts[optname] = optargfn(optarg)
 
688
                mutter("    option argument %r" % opts[optname])
462
689
            else:
463
690
                if optarg != None:
464
 
                    raise BzrError('option %r takes no argument' % optname)
 
691
                    bailout('option %r takes no argument' % optname)
465
692
                opts[optname] = True
466
693
        else:
467
694
            args.append(a)
470
697
 
471
698
 
472
699
 
 
700
def _match_args(cmd, args):
 
701
    """Check non-option arguments match required pattern.
473
702
 
474
 
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, [])
475
714
    argdict = {}
 
715
    # TODO: Need a way to express 'cp SRC... DEST', where it matches
 
716
    # all but one.
476
717
 
477
 
    # step through args and takes_args, allowing appropriate 0-many matches
478
 
    for ap in takes_args:
 
718
    # step through args and argform, allowing appropriate 0-many matches
 
719
    for ap in argform:
479
720
        argname = ap[:-1]
480
721
        if ap[-1] == '?':
481
722
            if args:
482
723
                argdict[argname] = args.pop(0)
483
 
        elif ap[-1] == '*': # all remaining arguments
484
 
            if args:
485
 
                argdict[argname + '_list'] = args[:]
486
 
                args = []
487
 
            else:
488
 
                argdict[argname + '_list'] = None
 
724
        elif ap[-1] == '*':
 
725
            assert 0
489
726
        elif ap[-1] == '+':
490
727
            if not args:
491
 
                raise BzrCommandError("command %r needs one or more %s"
 
728
                bailout("command %r needs one or more %s"
492
729
                        % (cmd, argname.upper()))
493
730
            else:
494
731
                argdict[argname + '_list'] = args[:]
495
732
                args = []
496
 
        elif ap[-1] == '$': # all but one
497
 
            if len(args) < 2:
498
 
                raise BzrCommandError("command %r needs one or more %s"
499
 
                        % (cmd, argname.upper()))
500
 
            argdict[argname + '_list'] = args[:-1]
501
 
            args[:-1] = []                
502
733
        else:
503
734
            # just a plain arg
504
735
            argname = ap
505
736
            if not args:
506
 
                raise BzrCommandError("command %r requires argument %s"
 
737
                bailout("command %r requires argument %s"
507
738
                        % (cmd, argname.upper()))
508
739
            else:
509
740
                argdict[argname] = args.pop(0)
510
741
            
511
742
    if args:
512
 
        raise BzrCommandError("extra argument to command %s: %s"
513
 
                              % (cmd, args[0]))
 
743
        bailout("extra arguments to command %s: %r"
 
744
                % (cmd, args))
514
745
 
515
746
    return argdict
516
747
 
517
748
 
518
749
 
519
 
def apply_profiled(the_callable, *args, **kwargs):
520
 
    import hotshot
521
 
    import tempfile
522
 
    import hotshot.stats
523
 
    pffileno, pfname = tempfile.mkstemp()
524
 
    try:
525
 
        prof = hotshot.Profile(pfname)
526
 
        try:
527
 
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
528
 
        finally:
529
 
            prof.close()
530
 
        stats = hotshot.stats.load(pfname)
531
 
        stats.strip_dirs()
532
 
        stats.sort_stats('cum')   # 'time'
533
 
        ## XXX: Might like to write to stderr or the trace file instead but
534
 
        ## print_stats seems hardcoded to stdout
535
 
        stats.print_stats(20)
536
 
        return ret
537
 
    finally:
538
 
        os.close(pffileno)
539
 
        os.remove(pfname)
540
 
 
541
 
 
542
750
def run_bzr(argv):
543
751
    """Execute a command.
544
752
 
545
753
    This is similar to main(), but without all the trappings for
546
 
    logging and error handling.  
547
 
    
548
 
    argv
549
 
       The command-line arguments, without the program name from argv[0]
550
 
    
551
 
    Returns a command status or raises an exception.
552
 
 
553
 
    Special master options: these must come before the command because
554
 
    they control how the command is interpreted.
555
 
 
556
 
    --no-plugins
557
 
        Do not load plugin modules at all
558
 
 
559
 
    --builtin
560
 
        Only use builtin commands.  (Plugins are still allowed to change
561
 
        other behaviour.)
562
 
 
563
 
    --profile
564
 
        Run under the Python profiler.
 
754
    logging and error handling.
565
755
    """
566
 
    # Load all of the transport methods
567
 
    import bzrlib.transport.local, bzrlib.transport.http
568
 
    
569
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
570
 
 
571
 
    opt_profile = opt_no_plugins = opt_builtin = False
572
 
 
573
 
    # --no-plugins is handled specially at a very early stage. We need
574
 
    # to load plugins before doing other command parsing so that they
575
 
    # can override commands, but this needs to happen first.
576
 
 
577
 
    for a in argv:
578
 
        if a == '--profile':
579
 
            opt_profile = True
580
 
        elif a == '--no-plugins':
581
 
            opt_no_plugins = True
582
 
        elif a == '--builtin':
583
 
            opt_builtin = True
584
 
        else:
585
 
            break
586
 
        argv.remove(a)
587
 
 
588
 
    if (not argv) or (argv[0] == '--help'):
589
 
        from bzrlib.help import help
590
 
        if len(argv) > 1:
591
 
            help(argv[1])
592
 
        else:
593
 
            help()
594
 
        return 0
595
 
 
596
 
    if argv[0] == '--version':
597
 
        from bzrlib.builtins import show_version
598
 
        show_version()
599
 
        return 0
600
 
        
601
 
    if not opt_no_plugins:
602
 
        from bzrlib.plugin import load_plugins
603
 
        load_plugins()
604
 
 
605
 
    cmd = str(argv.pop(0))
606
 
 
607
 
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
608
 
 
609
 
    if opt_profile:
610
 
        ret = apply_profiled(cmd_obj.run_argv, argv)
611
 
    else:
612
 
        ret = cmd_obj.run_argv(argv)
613
 
    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
 
614
791
 
615
792
 
616
793
def main(argv):
617
 
    import bzrlib.ui
618
 
    bzrlib.trace.log_startup(argv)
619
 
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
620
 
 
621
 
    return run_bzr_catch_errors(argv[1:])
622
 
 
623
 
 
624
 
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
    
625
802
    try:
626
 
        try:
627
 
            return run_bzr(argv)
628
 
        finally:
629
 
            # do this here inside the exception wrappers to catch EPIPE
630
 
            sys.stdout.flush()
631
 
    except BzrCommandError, e:
632
 
        # command line syntax error, etc
633
 
        log_error(str(e))
634
 
        return 1
 
803
        ret = run_bzr(argv)
 
804
        return ret
635
805
    except BzrError, e:
636
 
        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')
637
810
        return 1
638
 
    except AssertionError, e:
639
 
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
640
 
        return 3
641
 
    except KeyboardInterrupt, e:
642
 
        bzrlib.trace.log_exception('interrupted')
643
 
        return 2
644
811
    except Exception, e:
645
 
        import errno
646
 
        if (isinstance(e, IOError) 
647
 
            and hasattr(e, 'errno')
648
 
            and e.errno == errno.EPIPE):
649
 
            bzrlib.trace.note('broken pipe')
650
 
            return 2
651
 
        else:
652
 
            ## import pdb
653
 
            ## pdb.pm()
654
 
            bzrlib.trace.log_exception()
655
 
            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
 
 
820
 
656
821
 
657
822
if __name__ == '__main__':
658
823
    sys.exit(main(sys.argv))
 
824
    ##import profile
 
825
    ##profile.run('main(sys.argv)')
 
826