/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-08-04 22:04:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050804220440-99562df8151d1ac5
- add pending merge from aaron

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
 
19
 
import sys, os
 
18
# TODO: Split the command framework away from the actual commands.
 
19
 
 
20
# TODO: probably should say which arguments are candidates for glob
 
21
# expansion on windows and do that at the command level.
 
22
 
 
23
import sys
 
24
import os
20
25
 
21
26
import bzrlib
22
 
from bzrlib.trace import mutter, note, log_error
23
 
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
24
 
from bzrlib.osutils import quotefn
25
 
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
26
 
     format_date
 
27
from bzrlib.trace import mutter, note, log_error, warning
 
28
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
 
29
from bzrlib.branch import find_branch
 
30
from bzrlib import BZRDIR
 
31
 
 
32
 
 
33
plugin_cmds = {}
 
34
 
 
35
 
 
36
def register_command(cmd):
 
37
    "Utility function to help register a command"
 
38
    global plugin_cmds
 
39
    k = cmd.__name__
 
40
    if k.startswith("cmd_"):
 
41
        k_unsquished = _unsquish_command_name(k)
 
42
    else:
 
43
        k_unsquished = k
 
44
    if not plugin_cmds.has_key(k_unsquished):
 
45
        plugin_cmds[k_unsquished] = cmd
 
46
    else:
 
47
        log_error('Two plugins defined the same command: %r' % k)
 
48
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
27
49
 
28
50
 
29
51
def _squish_command_name(cmd):
34
56
    assert cmd.startswith("cmd_")
35
57
    return cmd[4:].replace('_','-')
36
58
 
 
59
 
37
60
def _parse_revision_str(revstr):
38
 
    """This handles a revision string -> revno. 
39
 
 
40
 
    There are several possibilities:
41
 
 
42
 
        '234'       -> 234
43
 
        '234:345'   -> [234, 345]
44
 
        ':234'      -> [None, 234]
45
 
        '234:'      -> [234, None]
46
 
 
47
 
    In the future we will also support:
48
 
        'uuid:blah-blah-blah'   -> ?
49
 
        'hash:blahblahblah'     -> ?
50
 
        potentially:
51
 
        'tag:mytag'             -> ?
 
61
    """This handles a revision string -> revno.
 
62
 
 
63
    This always returns a list.  The list will have one element for 
 
64
 
 
65
    It supports integers directly, but everything else it
 
66
    defers for passing to Branch.get_revision_info()
 
67
 
 
68
    >>> _parse_revision_str('234')
 
69
    [234]
 
70
    >>> _parse_revision_str('234..567')
 
71
    [234, 567]
 
72
    >>> _parse_revision_str('..')
 
73
    [None, None]
 
74
    >>> _parse_revision_str('..234')
 
75
    [None, 234]
 
76
    >>> _parse_revision_str('234..')
 
77
    [234, None]
 
78
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
 
79
    [234, 456, 789]
 
80
    >>> _parse_revision_str('234....789') # Error?
 
81
    [234, None, 789]
 
82
    >>> _parse_revision_str('revid:test@other.com-234234')
 
83
    ['revid:test@other.com-234234']
 
84
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
 
85
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
 
86
    >>> _parse_revision_str('revid:test@other.com-234234..23')
 
87
    ['revid:test@other.com-234234', 23]
 
88
    >>> _parse_revision_str('date:2005-04-12')
 
89
    ['date:2005-04-12']
 
90
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
 
91
    ['date:2005-04-12 12:24:33']
 
92
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
 
93
    ['date:2005-04-12T12:24:33']
 
94
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
 
95
    ['date:2005-04-12,12:24:33']
 
96
    >>> _parse_revision_str('-5..23')
 
97
    [-5, 23]
 
98
    >>> _parse_revision_str('-5')
 
99
    [-5]
 
100
    >>> _parse_revision_str('123a')
 
101
    ['123a']
 
102
    >>> _parse_revision_str('abc')
 
103
    ['abc']
52
104
    """
53
 
    if revstr.find(':') != -1:
54
 
        revs = revstr.split(':')
55
 
        if len(revs) > 2:
56
 
            raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
57
 
 
58
 
        if not revs[0]:
59
 
            revs[0] = None
60
 
        else:
61
 
            revs[0] = int(revs[0])
62
 
 
63
 
        if not revs[1]:
64
 
            revs[1] = None
65
 
        else:
66
 
            revs[1] = int(revs[1])
67
 
    else:
68
 
        revs = int(revstr)
 
105
    import re
 
106
    old_format_re = re.compile('\d*:\d*')
 
107
    m = old_format_re.match(revstr)
 
108
    if m:
 
109
        warning('Colon separator for revision numbers is deprecated.'
 
110
                ' Use .. instead')
 
111
        revs = []
 
112
        for rev in revstr.split(':'):
 
113
            if rev:
 
114
                revs.append(int(rev))
 
115
            else:
 
116
                revs.append(None)
 
117
        return revs
 
118
    revs = []
 
119
    for x in revstr.split('..'):
 
120
        if not x:
 
121
            revs.append(None)
 
122
        else:
 
123
            try:
 
124
                revs.append(int(x))
 
125
            except ValueError:
 
126
                revs.append(x)
69
127
    return revs
70
128
 
71
 
def _find_plugins():
72
 
    """Find all python files which are plugins, and load their commands
73
 
    to add to the list of "all commands"
74
129
 
75
 
    The environment variable BZRPATH is considered a delimited set of
76
 
    paths to look through. Each entry is searched for *.py files.
77
 
    If a directory is found, it is also searched, but they are 
78
 
    not searched recursively. This allows you to revctl the plugins.
 
130
def get_merge_type(typestring):
 
131
    """Attempt to find the merge class/factory associated with a string."""
 
132
    from merge import merge_types
 
133
    try:
 
134
        return merge_types[typestring][0]
 
135
    except KeyError:
 
136
        templ = '%s%%7s: %%s' % (' '*12)
 
137
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
138
        type_list = '\n'.join(lines)
 
139
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
140
            (typestring, type_list)
 
141
        raise BzrCommandError(msg)
79
142
    
80
 
    Inside the plugin should be a series of cmd_* function, which inherit from
81
 
    the bzrlib.commands.Command class.
82
 
    """
83
 
    bzrpath = os.environ.get('BZRPLUGINPATH', '')
84
 
 
85
 
    plugin_cmds = {} 
86
 
    if not bzrpath:
87
 
        return plugin_cmds
88
 
    _platform_extensions = {
89
 
        'win32':'.pyd',
90
 
        'cygwin':'.dll',
91
 
        'darwin':'.dylib',
92
 
        'linux2':'.so'
93
 
        }
94
 
    if _platform_extensions.has_key(sys.platform):
95
 
        platform_extension = _platform_extensions[sys.platform]
96
 
    else:
97
 
        platform_extension = None
98
 
    for d in bzrpath.split(os.pathsep):
99
 
        plugin_names = {} # This should really be a set rather than a dict
100
 
        for f in os.listdir(d):
101
 
            if f.endswith('.py'):
102
 
                f = f[:-3]
103
 
            elif f.endswith('.pyc') or f.endswith('.pyo'):
104
 
                f = f[:-4]
105
 
            elif platform_extension and f.endswith(platform_extension):
106
 
                f = f[:-len(platform_extension)]
107
 
                if f.endswidth('module'):
108
 
                    f = f[:-len('module')]
109
 
            else:
110
 
                continue
111
 
            if not plugin_names.has_key(f):
112
 
                plugin_names[f] = True
113
 
 
114
 
        plugin_names = plugin_names.keys()
115
 
        plugin_names.sort()
116
 
        try:
117
 
            sys.path.insert(0, d)
118
 
            for name in plugin_names:
119
 
                try:
120
 
                    old_module = None
121
 
                    try:
122
 
                        if sys.modules.has_key(name):
123
 
                            old_module = sys.modules[name]
124
 
                            del sys.modules[name]
125
 
                        plugin = __import__(name, locals())
126
 
                        for k in dir(plugin):
127
 
                            if k.startswith('cmd_'):
128
 
                                k_unsquished = _unsquish_command_name(k)
129
 
                                if not plugin_cmds.has_key(k_unsquished):
130
 
                                    plugin_cmds[k_unsquished] = getattr(plugin, k)
131
 
                                else:
132
 
                                    log_error('Two plugins defined the same command: %r' % k)
133
 
                                    log_error('Not loading the one in %r in dir %r' % (name, d))
134
 
                    finally:
135
 
                        if old_module:
136
 
                            sys.modules[name] = old_module
137
 
                except ImportError, e:
138
 
                    log_error('Unable to load plugin: %r from %r\n%s' % (name, d, e))
139
 
        finally:
140
 
            sys.path.pop(0)
141
 
    return plugin_cmds
142
 
 
143
 
def _get_cmd_dict(include_plugins=True):
 
143
 
 
144
 
 
145
def _get_cmd_dict(plugins_override=True):
144
146
    d = {}
145
147
    for k, v in globals().iteritems():
146
148
        if k.startswith("cmd_"):
147
149
            d[_unsquish_command_name(k)] = v
148
 
    if include_plugins:
149
 
        d.update(_find_plugins())
 
150
    # If we didn't load plugins, the plugin_cmds dict will be empty
 
151
    if plugins_override:
 
152
        d.update(plugin_cmds)
 
153
    else:
 
154
        d2 = plugin_cmds.copy()
 
155
        d2.update(d)
 
156
        d = d2
150
157
    return d
 
158
 
151
159
    
152
 
def get_all_cmds(include_plugins=True):
 
160
def get_all_cmds(plugins_override=True):
153
161
    """Return canonical name and class for all registered commands."""
154
 
    for k, v in _get_cmd_dict(include_plugins=include_plugins).iteritems():
 
162
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
155
163
        yield k,v
156
164
 
157
165
 
158
 
def get_cmd_class(cmd,include_plugins=True):
 
166
def get_cmd_class(cmd, plugins_override=True):
159
167
    """Return the canonical name and command class for a command.
160
168
    """
161
169
    cmd = str(cmd)                      # not unicode
162
170
 
163
171
    # first look up this command under the specified name
164
 
    cmds = _get_cmd_dict(include_plugins=include_plugins)
 
172
    cmds = _get_cmd_dict(plugins_override=plugins_override)
165
173
    try:
166
174
        return cmd, cmds[cmd]
167
175
    except KeyError:
211
219
        assert isinstance(arguments, dict)
212
220
        cmdargs = options.copy()
213
221
        cmdargs.update(arguments)
214
 
        assert self.__doc__ != Command.__doc__, \
215
 
               ("No help message set for %r" % self)
 
222
        if self.__doc__ == Command.__doc__:
 
223
            from warnings import warn
 
224
            warn("No help message set for %r" % self)
216
225
        self.status = self.run(**cmdargs)
 
226
        if self.status is None:
 
227
            self.status = 0
217
228
 
218
229
    
219
230
    def run(self):
231
242
class ExternalCommand(Command):
232
243
    """Class to wrap external commands.
233
244
 
234
 
    We cheat a little here, when get_cmd_class() calls us we actually give it back
235
 
    an object we construct that has the appropriate path, help, options etc for the
236
 
    specified command.
237
 
 
238
 
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
239
 
    method, which we override to call the Command.__init__ method. That then calls
240
 
    our run method which is pretty straight forward.
241
 
 
242
 
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
243
 
    back into command line options and arguments for the script.
 
245
    We cheat a little here, when get_cmd_class() calls us we actually
 
246
    give it back an object we construct that has the appropriate path,
 
247
    help, options etc for the specified command.
 
248
 
 
249
    When run_bzr() tries to instantiate that 'class' it gets caught by
 
250
    the __call__ method, which we override to call the Command.__init__
 
251
    method. That then calls our run method which is pretty straight
 
252
    forward.
 
253
 
 
254
    The only wrinkle is that we have to map bzr's dictionary of options
 
255
    and arguments back into command line options and arguments for the
 
256
    script.
244
257
    """
245
258
 
246
259
    def find_command(cls, cmd):
264
277
 
265
278
        for opt in self.takes_options:
266
279
            if not opt in OPTIONS:
267
 
                bailout("Unknown option '%s' returned by external command %s"
268
 
                    % (opt, path))
 
280
                raise BzrError("Unknown option '%s' returned by external command %s"
 
281
                               % (opt, path))
269
282
 
270
283
        # TODO: Is there any way to check takes_args is valid here?
271
284
        self.takes_args = pipe.readline().split()
272
285
 
273
286
        if pipe.close() is not None:
274
 
            bailout("Failed funning '%s --bzr-usage'" % path)
 
287
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
275
288
 
276
289
        pipe = os.popen('%s --bzr-help' % path, 'r')
277
290
        self.__doc__ = pipe.read()
278
291
        if pipe.close() is not None:
279
 
            bailout("Failed funning '%s --bzr-help'" % path)
 
292
            raise BzrError("Failed funning '%s --bzr-help'" % path)
280
293
 
281
294
    def __call__(self, options, arguments):
282
295
        Command.__init__(self, options, arguments)
343
356
    directory is shown.  Otherwise, only the status of the specified
344
357
    files or directories is reported.  If a directory is given, status
345
358
    is reported for everything inside that directory.
 
359
 
 
360
    If a revision is specified, the changes since that revision are shown.
346
361
    """
347
362
    takes_args = ['file*']
348
 
    takes_options = ['all', 'show-ids']
 
363
    takes_options = ['all', 'show-ids', 'revision']
349
364
    aliases = ['st', 'stat']
350
365
    
351
366
    def run(self, all=False, show_ids=False, file_list=None):
352
367
        if file_list:
353
 
            b = Branch(file_list[0])
 
368
            b = find_branch(file_list[0])
354
369
            file_list = [b.relpath(x) for x in file_list]
355
370
            # special case: only one path was given and it's the root
356
371
            # of the branch
357
372
            if file_list == ['']:
358
373
                file_list = None
359
374
        else:
360
 
            b = Branch('.')
361
 
        import status
362
 
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
363
 
                           specific_files=file_list)
 
375
            b = find_branch('.')
 
376
            
 
377
        from bzrlib.status import show_status
 
378
        show_status(b, show_unchanged=all, show_ids=show_ids,
 
379
                    specific_files=file_list)
364
380
 
365
381
 
366
382
class cmd_cat_revision(Command):
370
386
    takes_args = ['revision_id']
371
387
    
372
388
    def run(self, revision_id):
373
 
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
389
        from bzrlib.xml import pack_xml
 
390
        pack_xml(find_branch('.').get_revision(revision_id), sys.stdout)
374
391
 
375
392
 
376
393
class cmd_revno(Command):
378
395
 
379
396
    This is equal to the number of revisions on this branch."""
380
397
    def run(self):
381
 
        print Branch('.').revno()
 
398
        print find_branch('.').revno()
 
399
 
 
400
class cmd_revision_info(Command):
 
401
    """Show revision number and revision id for a given revision identifier.
 
402
    """
 
403
    hidden = True
 
404
    takes_args = ['revision_info*']
 
405
    takes_options = ['revision']
 
406
    def run(self, revision=None, revision_info_list=None):
 
407
        from bzrlib.branch import find_branch
 
408
 
 
409
        revs = []
 
410
        if revision is not None:
 
411
            revs.extend(revision)
 
412
        if revision_info_list is not None:
 
413
            revs.extend(revision_info_list)
 
414
        if len(revs) == 0:
 
415
            raise BzrCommandError('You must supply a revision identifier')
 
416
 
 
417
        b = find_branch('.')
 
418
 
 
419
        for rev in revs:
 
420
            print '%4d %s' % b.get_revision_info(rev)
382
421
 
383
422
    
384
423
class cmd_add(Command):
394
433
    whether already versioned or not, are searched for files or
395
434
    subdirectories that are neither versioned or ignored, and these
396
435
    are added.  This search proceeds recursively into versioned
397
 
    directories.
 
436
    directories.  If no names are given '.' is assumed.
398
437
 
399
 
    Therefore simply saying 'bzr add .' will version all files that
 
438
    Therefore simply saying 'bzr add' will version all files that
400
439
    are currently unknown.
401
440
 
402
441
    TODO: Perhaps adding a file whose directly is not versioned should
403
442
    recursively add that parent, rather than giving an error?
404
443
    """
405
 
    takes_args = ['file+']
 
444
    takes_args = ['file*']
406
445
    takes_options = ['verbose', 'no-recurse']
407
446
    
408
447
    def run(self, file_list, verbose=False, no_recurse=False):
409
 
        bzrlib.add.smart_add(file_list, verbose, not no_recurse)
 
448
        from bzrlib.add import smart_add
 
449
        smart_add(file_list, verbose, not no_recurse)
 
450
 
 
451
 
 
452
 
 
453
class cmd_mkdir(Command):
 
454
    """Create a new versioned directory.
 
455
 
 
456
    This is equivalent to creating the directory and then adding it.
 
457
    """
 
458
    takes_args = ['dir+']
 
459
 
 
460
    def run(self, dir_list):
 
461
        b = None
 
462
        
 
463
        for d in dir_list:
 
464
            os.mkdir(d)
 
465
            if not b:
 
466
                b = find_branch(d)
 
467
            b.add([d], verbose=True)
410
468
 
411
469
 
412
470
class cmd_relpath(Command):
415
473
    hidden = True
416
474
    
417
475
    def run(self, filename):
418
 
        print Branch(filename).relpath(filename)
 
476
        print find_branch(filename).relpath(filename)
419
477
 
420
478
 
421
479
 
424
482
    takes_options = ['revision', 'show-ids']
425
483
    
426
484
    def run(self, revision=None, show_ids=False):
427
 
        b = Branch('.')
 
485
        b = find_branch('.')
428
486
        if revision == None:
429
487
            inv = b.read_working_inventory()
430
488
        else:
431
 
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
489
            if len(revision) > 1:
 
490
                raise BzrCommandError('bzr inventory --revision takes'
 
491
                    ' exactly one revision identifier')
 
492
            inv = b.get_revision_inventory(b.lookup_revision(revision[0]))
432
493
 
433
494
        for path, entry in inv.entries():
434
495
            if show_ids:
447
508
    """
448
509
    takes_args = ['source$', 'dest']
449
510
    def run(self, source_list, dest):
450
 
        b = Branch('.')
 
511
        b = find_branch('.')
451
512
 
 
513
        # TODO: glob expansion on windows?
452
514
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
453
515
 
454
516
 
469
531
    takes_args = ['from_name', 'to_name']
470
532
    
471
533
    def run(self, from_name, to_name):
472
 
        b = Branch('.')
 
534
        b = find_branch('.')
473
535
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
474
536
 
475
537
 
476
538
 
 
539
class cmd_mv(Command):
 
540
    """Move or rename a file.
 
541
 
 
542
    usage:
 
543
        bzr mv OLDNAME NEWNAME
 
544
        bzr mv SOURCE... DESTINATION
 
545
 
 
546
    If the last argument is a versioned directory, all the other names
 
547
    are moved into it.  Otherwise, there must be exactly two arguments
 
548
    and the file is changed to a new name, which must not already exist.
 
549
 
 
550
    Files cannot be moved between branches.
 
551
    """
 
552
    takes_args = ['names*']
 
553
    def run(self, names_list):
 
554
        if len(names_list) < 2:
 
555
            raise BzrCommandError("missing file argument")
 
556
        b = find_branch(names_list[0])
 
557
 
 
558
        rel_names = [b.relpath(x) for x in names_list]
 
559
        
 
560
        if os.path.isdir(names_list[-1]):
 
561
            # move into existing directory
 
562
            b.move(rel_names[:-1], rel_names[-1])
 
563
        else:
 
564
            if len(names_list) != 2:
 
565
                raise BzrCommandError('to mv multiple files the destination '
 
566
                                      'must be a versioned directory')
 
567
            b.move(rel_names[0], rel_names[1])
 
568
            
 
569
    
477
570
 
478
571
 
479
572
class cmd_pull(Command):
494
587
 
495
588
    def run(self, location=None):
496
589
        from bzrlib.merge import merge
 
590
        import tempfile
 
591
        from shutil import rmtree
497
592
        import errno
498
593
        
499
 
        br_to = Branch('.')
 
594
        br_to = find_branch('.')
500
595
        stored_loc = None
501
596
        try:
502
597
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
503
598
        except IOError, e:
504
 
            if errno == errno.ENOENT:
 
599
            if e.errno != errno.ENOENT:
505
600
                raise
506
601
        if location is None:
507
 
            location = stored_loc
508
 
        if location is None:
509
 
            raise BzrCommandError("No pull location known or specified.")
510
 
        from branch import find_branch, DivergedBranches
 
602
            if stored_loc is None:
 
603
                raise BzrCommandError("No pull location known or specified.")
 
604
            else:
 
605
                print "Using last location: %s" % stored_loc
 
606
                location = stored_loc
 
607
        cache_root = tempfile.mkdtemp()
 
608
        from bzrlib.branch import DivergedBranches
511
609
        br_from = find_branch(location)
512
610
        location = pull_loc(br_from)
513
611
        old_revno = br_to.revno()
514
612
        try:
515
 
            br_to.update_revisions(br_from)
516
 
        except DivergedBranches:
517
 
            raise BzrCommandError("These branches have diverged.  Try merge.")
518
 
            
519
 
        merge(('.', -1), ('.', old_revno), check_clean=False)
520
 
        if location != stored_loc:
521
 
            br_to.controlfile("x-pull", "wb").write(location + "\n")
 
613
            from branch import find_cached_branch, DivergedBranches
 
614
            br_from = find_cached_branch(location, cache_root)
 
615
            location = pull_loc(br_from)
 
616
            old_revno = br_to.revno()
 
617
            try:
 
618
                br_to.update_revisions(br_from)
 
619
            except DivergedBranches:
 
620
                raise BzrCommandError("These branches have diverged."
 
621
                    "  Try merge.")
 
622
                
 
623
            merge(('.', -1), ('.', old_revno), check_clean=False)
 
624
            if location != stored_loc:
 
625
                br_to.controlfile("x-pull", "wb").write(location + "\n")
 
626
        finally:
 
627
            rmtree(cache_root)
522
628
 
523
629
 
524
630
 
533
639
    """
534
640
    takes_args = ['from_location', 'to_location?']
535
641
    takes_options = ['revision']
 
642
    aliases = ['get', 'clone']
536
643
 
537
644
    def run(self, from_location, to_location=None, revision=None):
538
645
        import errno
539
646
        from bzrlib.merge import merge
540
 
        from branch import find_branch, DivergedBranches, NoSuchRevision
 
647
        from bzrlib.branch import DivergedBranches, \
 
648
             find_cached_branch, Branch
541
649
        from shutil import rmtree
542
 
        try:
543
 
            br_from = find_branch(from_location)
544
 
        except OSError, e:
545
 
            if e.errno == errno.ENOENT:
546
 
                raise BzrCommandError('Source location "%s" does not exist.' %
547
 
                                      to_location)
548
 
            else:
549
 
                raise
550
 
 
551
 
        if to_location is None:
552
 
            to_location = os.path.basename(from_location.rstrip("/\\"))
553
 
 
554
 
        try:
555
 
            os.mkdir(to_location)
556
 
        except OSError, e:
557
 
            if e.errno == errno.EEXIST:
558
 
                raise BzrCommandError('Target directory "%s" already exists.' %
559
 
                                      to_location)
560
 
            if e.errno == errno.ENOENT:
561
 
                raise BzrCommandError('Parent of "%s" does not exist.' %
562
 
                                      to_location)
563
 
            else:
564
 
                raise
565
 
        br_to = Branch(to_location, init=True)
566
 
 
567
 
        try:
568
 
            br_to.update_revisions(br_from, stop_revision=revision)
569
 
        except NoSuchRevision:
570
 
            rmtree(to_location)
571
 
            msg = "The branch %s has no revision %d." % (from_location,
572
 
                                                         revision)
573
 
            raise BzrCommandError(msg)
574
 
        merge((to_location, -1), (to_location, 0), this_dir=to_location,
575
 
              check_clean=False)
576
 
        from_location = pull_loc(br_from)
577
 
        br_to.controlfile("x-pull", "wb").write(from_location + "\n")
 
650
        from meta_store import CachedStore
 
651
        import tempfile
 
652
        cache_root = tempfile.mkdtemp()
 
653
 
 
654
        if revision is None:
 
655
            revision = [None]
 
656
        elif len(revision) > 1:
 
657
            raise BzrCommandError('bzr branch --revision takes exactly 1 revision value')
 
658
 
 
659
        try:
 
660
            try:
 
661
                br_from = find_cached_branch(from_location, cache_root)
 
662
            except OSError, e:
 
663
                if e.errno == errno.ENOENT:
 
664
                    raise BzrCommandError('Source location "%s" does not'
 
665
                                          ' exist.' % to_location)
 
666
                else:
 
667
                    raise
 
668
 
 
669
            if to_location is None:
 
670
                to_location = os.path.basename(from_location.rstrip("/\\"))
 
671
 
 
672
            try:
 
673
                os.mkdir(to_location)
 
674
            except OSError, e:
 
675
                if e.errno == errno.EEXIST:
 
676
                    raise BzrCommandError('Target directory "%s" already'
 
677
                                          ' exists.' % to_location)
 
678
                if e.errno == errno.ENOENT:
 
679
                    raise BzrCommandError('Parent of "%s" does not exist.' %
 
680
                                          to_location)
 
681
                else:
 
682
                    raise
 
683
            br_to = Branch(to_location, init=True)
 
684
 
 
685
            br_to.set_root_id(br_from.get_root_id())
 
686
 
 
687
            if revision:
 
688
                if revision[0] is None:
 
689
                    revno = br_from.revno()
 
690
                else:
 
691
                    revno, rev_id = br_from.get_revision_info(revision[0])
 
692
                try:
 
693
                    br_to.update_revisions(br_from, stop_revision=revno)
 
694
                except bzrlib.errors.NoSuchRevision:
 
695
                    rmtree(to_location)
 
696
                    msg = "The branch %s has no revision %d." % (from_location,
 
697
                                                                 revno)
 
698
                    raise BzrCommandError(msg)
 
699
            
 
700
            merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
701
                  check_clean=False, ignore_zero=True)
 
702
            from_location = pull_loc(br_from)
 
703
            br_to.controlfile("x-pull", "wb").write(from_location + "\n")
 
704
        finally:
 
705
            rmtree(cache_root)
578
706
 
579
707
 
580
708
def pull_loc(branch):
597
725
    takes_args = ['dir?']
598
726
 
599
727
    def run(self, dir='.'):
600
 
        b = Branch(dir)
 
728
        b = find_branch(dir)
601
729
        old_inv = b.basis_tree().inventory
602
730
        new_inv = b.read_working_inventory()
603
731
 
614
742
    def run(self, branch=None):
615
743
        import info
616
744
 
617
 
        from branch import find_branch
618
745
        b = find_branch(branch)
619
746
        info.show_info(b)
620
747
 
629
756
    takes_options = ['verbose']
630
757
    
631
758
    def run(self, file_list, verbose=False):
632
 
        b = Branch(file_list[0])
 
759
        b = find_branch(file_list[0])
633
760
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
634
761
 
635
762
 
643
770
    hidden = True
644
771
    takes_args = ['filename']
645
772
    def run(self, filename):
646
 
        b = Branch(filename)
 
773
        b = find_branch(filename)
647
774
        i = b.inventory.path2id(b.relpath(filename))
648
775
        if i == None:
649
 
            bailout("%r is not a versioned file" % filename)
 
776
            raise BzrError("%r is not a versioned file" % filename)
650
777
        else:
651
778
            print i
652
779
 
659
786
    hidden = True
660
787
    takes_args = ['filename']
661
788
    def run(self, filename):
662
 
        b = Branch(filename)
 
789
        b = find_branch(filename)
663
790
        inv = b.inventory
664
791
        fid = inv.path2id(b.relpath(filename))
665
792
        if fid == None:
666
 
            bailout("%r is not a versioned file" % filename)
 
793
            raise BzrError("%r is not a versioned file" % filename)
667
794
        for fip in inv.get_idpath(fid):
668
795
            print fip
669
796
 
672
799
    """Display list of revision ids on this branch."""
673
800
    hidden = True
674
801
    def run(self):
675
 
        for patchid in Branch('.').revision_history():
 
802
        for patchid in find_branch('.').revision_history():
676
803
            print patchid
677
804
 
678
805
 
679
806
class cmd_directories(Command):
680
807
    """Display list of versioned directories in this branch."""
681
808
    def run(self):
682
 
        for name, ie in Branch('.').read_working_inventory().directories():
 
809
        for name, ie in find_branch('.').read_working_inventory().directories():
683
810
            if name == '':
684
811
                print '.'
685
812
            else:
700
827
        bzr commit -m 'imported project'
701
828
    """
702
829
    def run(self):
 
830
        from bzrlib.branch import Branch
703
831
        Branch('.', init=True)
704
832
 
705
833
 
733
861
 
734
862
    def run(self, revision=None, file_list=None, diff_options=None):
735
863
        from bzrlib.diff import show_diff
736
 
        from bzrlib import find_branch
737
864
 
738
865
        if file_list:
739
866
            b = find_branch(file_list[0])
742
869
                # just pointing to top-of-tree
743
870
                file_list = None
744
871
        else:
745
 
            b = Branch('.')
 
872
            b = find_branch('.')
 
873
 
 
874
        # TODO: Make show_diff support taking 2 arguments
 
875
        base_rev = None
 
876
        if revision is not None:
 
877
            if len(revision) != 1:
 
878
                raise BzrCommandError('bzr diff --revision takes exactly one revision identifier')
 
879
            base_rev = revision[0]
746
880
    
747
 
        show_diff(b, revision, specific_files=file_list,
 
881
        show_diff(b, base_rev, specific_files=file_list,
748
882
                  external_diff_options=diff_options)
749
883
 
750
884
 
757
891
    TODO: Show files deleted since a previous revision, or between two revisions.
758
892
    """
759
893
    def run(self, show_ids=False):
760
 
        b = Branch('.')
 
894
        b = find_branch('.')
761
895
        old = b.basis_tree()
762
896
        new = b.working_tree()
763
897
 
778
912
    """List files modified in working tree."""
779
913
    hidden = True
780
914
    def run(self):
781
 
        import statcache
782
 
        b = Branch('.')
783
 
        inv = b.read_working_inventory()
784
 
        sc = statcache.update_cache(b, inv)
785
 
        basis = b.basis_tree()
786
 
        basis_inv = basis.inventory
787
 
        
788
 
        # We used to do this through iter_entries(), but that's slow
789
 
        # when most of the files are unmodified, as is usually the
790
 
        # case.  So instead we iterate by inventory entry, and only
791
 
        # calculate paths as necessary.
792
 
 
793
 
        for file_id in basis_inv:
794
 
            cacheentry = sc.get(file_id)
795
 
            if not cacheentry:                 # deleted
796
 
                continue
797
 
            ie = basis_inv[file_id]
798
 
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
799
 
                path = inv.id2path(file_id)
800
 
                print path
 
915
        from bzrlib.delta import compare_trees
 
916
 
 
917
        b = find_branch('.')
 
918
        td = compare_trees(b.basis_tree(), b.working_tree())
 
919
 
 
920
        for path, id, kind in td.modified:
 
921
            print path
801
922
 
802
923
 
803
924
 
805
926
    """List files added in working tree."""
806
927
    hidden = True
807
928
    def run(self):
808
 
        b = Branch('.')
 
929
        b = find_branch('.')
809
930
        wt = b.working_tree()
810
931
        basis_inv = b.basis_tree().inventory
811
932
        inv = wt.inventory
827
948
    takes_args = ['filename?']
828
949
    def run(self, filename=None):
829
950
        """Print the branch root."""
830
 
        from branch import find_branch
831
951
        b = find_branch(filename)
832
952
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
833
953
 
839
959
    -r revision requests a specific revision, -r :end or -r begin: are
840
960
    also valid.
841
961
 
 
962
    --message allows you to give a regular expression, which will be evaluated
 
963
    so that only matching entries will be displayed.
 
964
 
842
965
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
843
966
  
844
967
    """
845
968
 
846
969
    takes_args = ['filename?']
847
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
 
970
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision','long', 'message']
848
971
    
849
972
    def run(self, filename=None, timezone='original',
850
973
            verbose=False,
851
974
            show_ids=False,
852
975
            forward=False,
853
 
            revision=None):
854
 
        from bzrlib import show_log, find_branch
 
976
            revision=None,
 
977
            message=None,
 
978
            long=False):
 
979
        from bzrlib.branch import find_branch
 
980
        from bzrlib.log import log_formatter, show_log
855
981
        import codecs
856
982
 
857
983
        direction = (forward and 'forward') or 'reverse'
867
993
            b = find_branch('.')
868
994
            file_id = None
869
995
 
870
 
        if revision == None:
871
 
            revision = [None, None]
872
 
        elif isinstance(revision, int):
873
 
            revision = [revision, revision]
 
996
        if revision is None:
 
997
            rev1 = None
 
998
            rev2 = None
 
999
        elif len(revision) == 1:
 
1000
            rev1 = rev2 = b.get_revision_info(revision[0])[0]
 
1001
        elif len(revision) == 2:
 
1002
            rev1 = b.get_revision_info(revision[0])[0]
 
1003
            rev2 = b.get_revision_info(revision[1])[0]
874
1004
        else:
875
 
            # pair of revisions?
876
 
            pass
877
 
            
878
 
        assert len(revision) == 2
 
1005
            raise BzrCommandError('bzr log --revision takes one or two values.')
 
1006
 
 
1007
        if rev1 == 0:
 
1008
            rev1 = None
 
1009
        if rev2 == 0:
 
1010
            rev2 = None
879
1011
 
880
1012
        mutter('encoding log as %r' % bzrlib.user_encoding)
881
1013
 
883
1015
        # in e.g. the default C locale.
884
1016
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
885
1017
 
886
 
        show_log(b, file_id,
887
 
                 show_timezone=timezone,
 
1018
        if long:
 
1019
            log_format = 'long'
 
1020
        else:
 
1021
            log_format = 'short'
 
1022
        lf = log_formatter(log_format,
 
1023
                           show_ids=show_ids,
 
1024
                           to_file=outf,
 
1025
                           show_timezone=timezone)
 
1026
 
 
1027
        show_log(b,
 
1028
                 lf,
 
1029
                 file_id,
888
1030
                 verbose=verbose,
889
 
                 show_ids=show_ids,
890
 
                 to_file=outf,
891
1031
                 direction=direction,
892
 
                 start_revision=revision[0],
893
 
                 end_revision=revision[1])
 
1032
                 start_revision=rev1,
 
1033
                 end_revision=rev2,
 
1034
                 search=message)
894
1035
 
895
1036
 
896
1037
 
901
1042
    hidden = True
902
1043
    takes_args = ["filename"]
903
1044
    def run(self, filename):
904
 
        b = Branch(filename)
 
1045
        b = find_branch(filename)
905
1046
        inv = b.read_working_inventory()
906
1047
        file_id = inv.path2id(b.relpath(filename))
907
1048
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
915
1056
    """
916
1057
    hidden = True
917
1058
    def run(self, revision=None, verbose=False):
918
 
        b = Branch('.')
 
1059
        b = find_branch('.')
919
1060
        if revision == None:
920
1061
            tree = b.working_tree()
921
1062
        else:
939
1080
class cmd_unknowns(Command):
940
1081
    """List unknown files."""
941
1082
    def run(self):
942
 
        for f in Branch('.').unknowns():
 
1083
        from bzrlib.osutils import quotefn
 
1084
        for f in find_branch('.').unknowns():
943
1085
            print quotefn(f)
944
1086
 
945
1087
 
967
1109
        from bzrlib.atomicfile import AtomicFile
968
1110
        import os.path
969
1111
 
970
 
        b = Branch('.')
 
1112
        b = find_branch('.')
971
1113
        ifn = b.abspath('.bzrignore')
972
1114
 
973
1115
        if os.path.exists(ifn):
1007
1149
 
1008
1150
    See also: bzr ignore"""
1009
1151
    def run(self):
1010
 
        tree = Branch('.').working_tree()
 
1152
        tree = find_branch('.').working_tree()
1011
1153
        for path, file_class, kind, file_id in tree.list_files():
1012
1154
            if file_class != 'I':
1013
1155
                continue
1031
1173
        except ValueError:
1032
1174
            raise BzrCommandError("not a valid revision-number: %r" % revno)
1033
1175
 
1034
 
        print Branch('.').lookup_revision(revno)
 
1176
        print find_branch('.').lookup_revision(revno)
1035
1177
 
1036
1178
 
1037
1179
class cmd_export(Command):
1040
1182
    If no revision is specified this exports the last committed revision.
1041
1183
 
1042
1184
    Format may be an "exporter" name, such as tar, tgz, tbz2.  If none is
1043
 
    given, exports to a directory (equivalent to --format=dir)."""
 
1185
    given, try to find the format with the extension. If no extension
 
1186
    is found exports to a directory (equivalent to --format=dir).
 
1187
 
 
1188
    Root may be the top directory for tar, tgz and tbz2 formats. If none
 
1189
    is given, the top directory will be the root name of the file."""
1044
1190
    # TODO: list known exporters
1045
1191
    takes_args = ['dest']
1046
 
    takes_options = ['revision', 'format']
1047
 
    def run(self, dest, revision=None, format='dir'):
1048
 
        b = Branch('.')
1049
 
        if revision == None:
1050
 
            rh = b.revision_history()[-1]
 
1192
    takes_options = ['revision', 'format', 'root']
 
1193
    def run(self, dest, revision=None, format=None, root=None):
 
1194
        import os.path
 
1195
        b = find_branch('.')
 
1196
        if revision is None:
 
1197
            rev_id = b.last_patch()
1051
1198
        else:
1052
 
            rh = b.lookup_revision(int(revision))
1053
 
        t = b.revision_tree(rh)
1054
 
        t.export(dest, format)
 
1199
            if len(revision) != 1:
 
1200
                raise BzrError('bzr export --revision takes exactly 1 argument')
 
1201
            revno, rev_id = b.get_revision_info(revision[0])
 
1202
        t = b.revision_tree(rev_id)
 
1203
        root, ext = os.path.splitext(dest)
 
1204
        if not format:
 
1205
            if ext in (".tar",):
 
1206
                format = "tar"
 
1207
            elif ext in (".gz", ".tgz"):
 
1208
                format = "tgz"
 
1209
            elif ext in (".bz2", ".tbz2"):
 
1210
                format = "tbz2"
 
1211
            else:
 
1212
                format = "dir"
 
1213
        t.export(dest, format, root)
1055
1214
 
1056
1215
 
1057
1216
class cmd_cat(Command):
1063
1222
    def run(self, filename, revision=None):
1064
1223
        if revision == None:
1065
1224
            raise BzrCommandError("bzr cat requires a revision number")
1066
 
        b = Branch('.')
1067
 
        b.print_file(b.relpath(filename), int(revision))
 
1225
        elif len(revision) != 1:
 
1226
            raise BzrCommandError("bzr cat --revision takes exactly one number")
 
1227
        b = find_branch('.')
 
1228
        b.print_file(b.relpath(filename), revision[0])
1068
1229
 
1069
1230
 
1070
1231
class cmd_local_time_offset(Command):
1077
1238
 
1078
1239
class cmd_commit(Command):
1079
1240
    """Commit changes into a new revision.
 
1241
    
 
1242
    If no arguments are given, the entire tree is committed.
1080
1243
 
1081
1244
    If selected files are specified, only changes to those files are
1082
 
    committed.  If a directory is specified then its contents are also
1083
 
    committed.
 
1245
    committed.  If a directory is specified then the directory and everything 
 
1246
    within it is committed.
1084
1247
 
1085
1248
    A selected-file commit may fail in some cases where the committed
1086
1249
    tree would be invalid, such as trying to commit a file in a
1091
1254
    TODO: Strict commit that fails if there are unknown or deleted files.
1092
1255
    """
1093
1256
    takes_args = ['selected*']
1094
 
    takes_options = ['message', 'file', 'verbose']
 
1257
    takes_options = ['message', 'file', 'verbose', 'unchanged']
1095
1258
    aliases = ['ci', 'checkin']
1096
1259
 
1097
 
    def run(self, message=None, file=None, verbose=True, selected_list=None):
1098
 
        from bzrlib.commit import commit
 
1260
    # TODO: Give better message for -s, --summary, used by tla people
 
1261
    
 
1262
    def run(self, message=None, file=None, verbose=True, selected_list=None,
 
1263
            unchanged=False):
 
1264
        from bzrlib.errors import PointlessCommit
 
1265
        from bzrlib.osutils import get_text_message
1099
1266
 
1100
1267
        ## Warning: shadows builtin file()
1101
1268
        if not message and not file:
1102
 
            raise BzrCommandError("please specify a commit message",
1103
 
                                  ["use either --message or --file"])
 
1269
            # FIXME: Ugly; change status code to send to a provided function?
 
1270
            
 
1271
            import cStringIO
 
1272
            stdout = sys.stdout
 
1273
            catcher = cStringIO.StringIO()
 
1274
            sys.stdout = catcher
 
1275
            cmd_status({"file_list":selected_list}, {})
 
1276
            info = catcher.getvalue()
 
1277
            sys.stdout = stdout
 
1278
            message = get_text_message(info)
 
1279
            
 
1280
            if message is None:
 
1281
                raise BzrCommandError("please specify a commit message",
 
1282
                                      ["use either --message or --file"])
1104
1283
        elif message and file:
1105
1284
            raise BzrCommandError("please specify either --message or --file")
1106
1285
        
1108
1287
            import codecs
1109
1288
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1110
1289
 
1111
 
        b = Branch('.')
1112
 
        commit(b, message, verbose=verbose, specific_files=selected_list)
 
1290
        b = find_branch('.')
 
1291
        if selected_list:
 
1292
            selected_list = [b.relpath(s) for s in selected_list]
 
1293
            
 
1294
        try:
 
1295
            b.commit(message, verbose=verbose,
 
1296
                     specific_files=selected_list,
 
1297
                     allow_pointless=unchanged)
 
1298
        except PointlessCommit:
 
1299
            # FIXME: This should really happen before the file is read in;
 
1300
            # perhaps prepare the commit; get the message; then actually commit
 
1301
            raise BzrCommandError("no changes to commit",
 
1302
                                  ["use --unchanged to commit anyhow"])
1113
1303
 
1114
1304
 
1115
1305
class cmd_check(Command):
1122
1312
    to help ensure data consistency.
1123
1313
    """
1124
1314
    takes_args = ['dir?']
1125
 
    takes_options = ['update']
1126
 
 
1127
 
    def run(self, dir='.', update=False):
1128
 
        import bzrlib.check
1129
 
        bzrlib.check.check(Branch(dir), update=update)
 
1315
 
 
1316
    def run(self, dir='.'):
 
1317
        from bzrlib.check import check
 
1318
        check(find_branch(dir))
 
1319
 
 
1320
 
 
1321
 
 
1322
class cmd_scan_cache(Command):
 
1323
    hidden = True
 
1324
    def run(self):
 
1325
        from bzrlib.hashcache import HashCache
 
1326
        import os
 
1327
 
 
1328
        c = HashCache('.')
 
1329
        c.read()
 
1330
        c.scan()
 
1331
            
 
1332
        print '%6d stats' % c.stat_count
 
1333
        print '%6d in hashcache' % len(c._cache)
 
1334
        print '%6d files removed from cache' % c.removed_count
 
1335
        print '%6d hashes updated' % c.update_count
 
1336
        print '%6d files changed too recently to cache' % c.danger_count
 
1337
 
 
1338
        if c.needs_write:
 
1339
            c.write()
 
1340
            
 
1341
 
 
1342
 
 
1343
class cmd_upgrade(Command):
 
1344
    """Upgrade branch storage to current format.
 
1345
 
 
1346
    This should normally be used only after the check command tells
 
1347
    you to run it.
 
1348
    """
 
1349
    takes_args = ['dir?']
 
1350
 
 
1351
    def run(self, dir='.'):
 
1352
        from bzrlib.upgrade import upgrade
 
1353
        upgrade(find_branch(dir))
1130
1354
 
1131
1355
 
1132
1356
 
1144
1368
class cmd_selftest(Command):
1145
1369
    """Run internal test suite"""
1146
1370
    hidden = True
1147
 
    def run(self):
 
1371
    takes_options = ['verbose']
 
1372
    def run(self, verbose=False):
1148
1373
        from bzrlib.selftest import selftest
1149
 
        if selftest():
1150
 
            return 0
1151
 
        else:
1152
 
            return 1
1153
 
 
 
1374
        return int(not selftest(verbose=verbose))
1154
1375
 
1155
1376
 
1156
1377
class cmd_version(Command):
1188
1409
    ['..', -1]
1189
1410
    >>> parse_spec("../f/@35")
1190
1411
    ['../f', 35]
 
1412
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
1413
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
1191
1414
    """
1192
1415
    if spec is None:
1193
1416
        return [None, None]
1197
1420
        if parsed[1] == "":
1198
1421
            parsed[1] = -1
1199
1422
        else:
1200
 
            parsed[1] = int(parsed[1])
1201
 
            assert parsed[1] >=0
 
1423
            try:
 
1424
                parsed[1] = int(parsed[1])
 
1425
            except ValueError:
 
1426
                pass # We can allow stuff like ./@revid:blahblahblah
 
1427
            else:
 
1428
                assert parsed[1] >=0
1202
1429
    else:
1203
1430
        parsed = [spec, None]
1204
1431
    return parsed
1228
1455
    --force is given.
1229
1456
    """
1230
1457
    takes_args = ['other_spec', 'base_spec?']
1231
 
    takes_options = ['force']
 
1458
    takes_options = ['force', 'merge-type']
1232
1459
 
1233
 
    def run(self, other_spec, base_spec=None, force=False):
 
1460
    def run(self, other_spec, base_spec=None, force=False, merge_type=None):
1234
1461
        from bzrlib.merge import merge
 
1462
        from bzrlib.merge_core import ApplyMerge3
 
1463
        if merge_type is None:
 
1464
            merge_type = ApplyMerge3
1235
1465
        merge(parse_spec(other_spec), parse_spec(base_spec),
1236
 
              check_clean=(not force))
 
1466
              check_clean=(not force), merge_type=merge_type)
1237
1467
 
1238
1468
 
1239
1469
class cmd_revert(Command):
1240
1470
    """Reverse all changes since the last commit.
1241
1471
 
1242
 
    Only versioned files are affected.
1243
 
 
1244
 
    TODO: Store backups of any files that will be reverted, so
1245
 
          that the revert can be undone.          
 
1472
    Only versioned files are affected.  Specify filenames to revert only 
 
1473
    those files.  By default, any files that are changed will be backed up
 
1474
    first.  Backup files have a '~' appended to their name.
1246
1475
    """
1247
 
    takes_options = ['revision']
 
1476
    takes_options = ['revision', 'no-backup']
 
1477
    takes_args = ['file*']
 
1478
    aliases = ['merge-revert']
1248
1479
 
1249
 
    def run(self, revision=-1):
 
1480
    def run(self, revision=None, no_backup=False, file_list=None):
1250
1481
        from bzrlib.merge import merge
1251
 
        merge(('.', revision), parse_spec('.'),
 
1482
        if file_list is not None:
 
1483
            if len(file_list) == 0:
 
1484
                raise BzrCommandError("No files specified")
 
1485
        if revision is None:
 
1486
            revision = [-1]
 
1487
        elif len(revision) != 1:
 
1488
            raise BzrCommandError('bzr revert --revision takes exactly 1 argument')
 
1489
        merge(('.', revision[0]), parse_spec('.'),
1252
1490
              check_clean=False,
1253
 
              ignore_zero=True)
 
1491
              ignore_zero=True,
 
1492
              backup_files=not no_backup,
 
1493
              file_list=file_list)
1254
1494
 
1255
1495
 
1256
1496
class cmd_assert_fail(Command):
1272
1512
        help.help(topic)
1273
1513
 
1274
1514
 
1275
 
class cmd_update_stat_cache(Command):
1276
 
    """Update stat-cache mapping inodes to SHA-1 hashes.
1277
 
 
1278
 
    For testing only."""
 
1515
 
 
1516
 
 
1517
class cmd_plugins(Command):
 
1518
    """List plugins"""
1279
1519
    hidden = True
1280
1520
    def run(self):
1281
 
        import statcache
1282
 
        b = Branch('.')
1283
 
        statcache.update_cache(b.base, b.read_working_inventory())
 
1521
        import bzrlib.plugin
 
1522
        from inspect import getdoc
 
1523
        from pprint import pprint
 
1524
        for plugin in bzrlib.plugin.all_plugins:
 
1525
            print plugin.__path__[0]
 
1526
            d = getdoc(plugin)
 
1527
            if d:
 
1528
                print '\t', d.split('\n')[0]
 
1529
 
 
1530
        #pprint(bzrlib.plugin.all_plugins)
1284
1531
 
1285
1532
 
1286
1533
 
1304
1551
    'verbose':                None,
1305
1552
    'version':                None,
1306
1553
    'email':                  None,
 
1554
    'unchanged':              None,
1307
1555
    'update':                 None,
 
1556
    'long':                   None,
 
1557
    'root':                   str,
 
1558
    'no-backup':              None,
 
1559
    'merge-type':             get_merge_type,
1308
1560
    }
1309
1561
 
1310
1562
SHORT_OPTIONS = {
1313
1565
    'm':                      'message',
1314
1566
    'r':                      'revision',
1315
1567
    'v':                      'verbose',
 
1568
    'l':                      'long',
1316
1569
}
1317
1570
 
1318
1571
 
1333
1586
    >>> parse_args('commit --message=biter'.split())
1334
1587
    (['commit'], {'message': u'biter'})
1335
1588
    >>> parse_args('log -r 500'.split())
1336
 
    (['log'], {'revision': 500})
1337
 
    >>> parse_args('log -r500:600'.split())
 
1589
    (['log'], {'revision': [500]})
 
1590
    >>> parse_args('log -r500..600'.split())
1338
1591
    (['log'], {'revision': [500, 600]})
1339
 
    >>> parse_args('log -vr500:600'.split())
 
1592
    >>> parse_args('log -vr500..600'.split())
1340
1593
    (['log'], {'verbose': True, 'revision': [500, 600]})
1341
 
    >>> parse_args('log -rv500:600'.split()) #the r takes an argument
1342
 
    Traceback (most recent call last):
1343
 
    ...
1344
 
    ValueError: invalid literal for int(): v500
 
1594
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
1595
    (['log'], {'revision': ['v500', 600]})
1345
1596
    """
1346
1597
    args = []
1347
1598
    opts = {}
1361
1612
                else:
1362
1613
                    optname = a[2:]
1363
1614
                if optname not in OPTIONS:
1364
 
                    bailout('unknown long option %r' % a)
 
1615
                    raise BzrError('unknown long option %r' % a)
1365
1616
            else:
1366
1617
                shortopt = a[1:]
1367
1618
                if shortopt in SHORT_OPTIONS:
1375
1626
                    if shortopt not in SHORT_OPTIONS:
1376
1627
                        # We didn't find the multi-character name, and we
1377
1628
                        # didn't find the single char name
1378
 
                        bailout('unknown short option %r' % a)
 
1629
                        raise BzrError('unknown short option %r' % a)
1379
1630
                    optname = SHORT_OPTIONS[shortopt]
1380
1631
 
1381
1632
                    if a[2:]:
1395
1646
            
1396
1647
            if optname in opts:
1397
1648
                # XXX: Do we ever want to support this, e.g. for -r?
1398
 
                bailout('repeated option %r' % a)
 
1649
                raise BzrError('repeated option %r' % a)
1399
1650
                
1400
1651
            optargfn = OPTIONS[optname]
1401
1652
            if optargfn:
1402
1653
                if optarg == None:
1403
1654
                    if not argv:
1404
 
                        bailout('option %r needs an argument' % a)
 
1655
                        raise BzrError('option %r needs an argument' % a)
1405
1656
                    else:
1406
1657
                        optarg = argv.pop(0)
1407
1658
                opts[optname] = optargfn(optarg)
1408
1659
            else:
1409
1660
                if optarg != None:
1410
 
                    bailout('option %r takes no argument' % optname)
 
1661
                    raise BzrError('option %r takes no argument' % optname)
1411
1662
                opts[optname] = True
1412
1663
        else:
1413
1664
            args.append(a)
1467
1718
 
1468
1719
    This is similar to main(), but without all the trappings for
1469
1720
    logging and error handling.  
 
1721
    
 
1722
    argv
 
1723
       The command-line arguments, without the program name from argv[0]
 
1724
    
 
1725
    Returns a command status or raises an exception.
 
1726
 
 
1727
    Special master options: these must come before the command because
 
1728
    they control how the command is interpreted.
 
1729
 
 
1730
    --no-plugins
 
1731
        Do not load plugin modules at all
 
1732
 
 
1733
    --builtin
 
1734
        Only use builtin commands.  (Plugins are still allowed to change
 
1735
        other behaviour.)
 
1736
 
 
1737
    --profile
 
1738
        Run under the Python profiler.
1470
1739
    """
 
1740
    
1471
1741
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
1742
 
 
1743
    opt_profile = opt_no_plugins = opt_builtin = False
 
1744
 
 
1745
    # --no-plugins is handled specially at a very early stage. We need
 
1746
    # to load plugins before doing other command parsing so that they
 
1747
    # can override commands, but this needs to happen first.
 
1748
 
 
1749
    for a in argv[:]:
 
1750
        if a == '--profile':
 
1751
            opt_profile = True
 
1752
        elif a == '--no-plugins':
 
1753
            opt_no_plugins = True
 
1754
        elif a == '--builtin':
 
1755
            opt_builtin = True
 
1756
        else:
 
1757
            break
 
1758
        argv.remove(a)
 
1759
 
 
1760
    if not opt_no_plugins:
 
1761
        from bzrlib.plugin import load_plugins
 
1762
        load_plugins()
 
1763
 
 
1764
    args, opts = parse_args(argv)
 
1765
 
 
1766
    if 'help' in opts:
 
1767
        from bzrlib.help import help
 
1768
        if args:
 
1769
            help(args[0])
 
1770
        else:
 
1771
            help()
 
1772
        return 0            
 
1773
        
 
1774
    if 'version' in opts:
 
1775
        show_version()
 
1776
        return 0
1472
1777
    
1473
 
    include_plugins=True
1474
 
    try:
1475
 
        args, opts = parse_args(argv[1:])
1476
 
        if 'help' in opts:
1477
 
            import help
1478
 
            if args:
1479
 
                help.help(args[0])
1480
 
            else:
1481
 
                help.help()
1482
 
            return 0
1483
 
        elif 'version' in opts:
1484
 
            show_version()
1485
 
            return 0
1486
 
        elif args and args[0] == 'builtin':
1487
 
            include_plugins=False
1488
 
            args = args[1:]
1489
 
        cmd = str(args.pop(0))
1490
 
    except IndexError:
1491
 
        import help
1492
 
        help.help()
 
1778
    if not args:
 
1779
        print >>sys.stderr, "please try 'bzr help' for help"
1493
1780
        return 1
1494
 
          
1495
 
 
1496
 
    canonical_cmd, cmd_class = get_cmd_class(cmd,include_plugins=include_plugins)
1497
 
 
1498
 
    # global option
1499
 
    if 'profile' in opts:
1500
 
        profile = True
1501
 
        del opts['profile']
1502
 
    else:
1503
 
        profile = False
 
1781
    
 
1782
    cmd = str(args.pop(0))
 
1783
 
 
1784
    canonical_cmd, cmd_class = \
 
1785
                   get_cmd_class(cmd, plugins_override=not opt_builtin)
1504
1786
 
1505
1787
    # check options are reasonable
1506
1788
    allowed = cmd_class.takes_options
1515
1797
    for k, v in opts.items():
1516
1798
        cmdopts[k.replace('-', '_')] = v
1517
1799
 
1518
 
    if profile:
 
1800
    if opt_profile:
1519
1801
        import hotshot, tempfile
1520
1802
        pffileno, pfname = tempfile.mkstemp()
1521
1803
        try:
1542
1824
 
1543
1825
def _report_exception(summary, quiet=False):
1544
1826
    import traceback
 
1827
    
1545
1828
    log_error('bzr: ' + summary)
1546
1829
    bzrlib.trace.log_exception()
1547
1830
 
 
1831
    if os.environ.get('BZR_DEBUG'):
 
1832
        traceback.print_exc()
 
1833
 
1548
1834
    if not quiet:
 
1835
        sys.stderr.write('\n')
1549
1836
        tb = sys.exc_info()[2]
1550
1837
        exinfo = traceback.extract_tb(tb)
1551
1838
        if exinfo:
1555
1842
 
1556
1843
 
1557
1844
def main(argv):
1558
 
    import errno
1559
1845
    
1560
 
    bzrlib.open_tracefile(argv)
 
1846
    bzrlib.trace.open_tracefile(argv)
1561
1847
 
1562
1848
    try:
1563
1849
        try:
1564
1850
            try:
1565
 
                return run_bzr(argv)
 
1851
                return run_bzr(argv[1:])
1566
1852
            finally:
1567
1853
                # do this here inside the exception wrappers to catch EPIPE
1568
1854
                sys.stdout.flush()
1569
1855
        except BzrError, e:
1570
1856
            quiet = isinstance(e, (BzrCommandError))
1571
 
            _report_exception('error: ' + e.args[0], quiet=quiet)
 
1857
            _report_exception('error: ' + str(e), quiet=quiet)
1572
1858
            if len(e.args) > 1:
1573
1859
                for h in e.args[1]:
1574
1860
                    # some explanation or hints
1584
1870
            _report_exception('interrupted', quiet=True)
1585
1871
            return 2
1586
1872
        except Exception, e:
 
1873
            import errno
1587
1874
            quiet = False
1588
1875
            if (isinstance(e, IOError) 
1589
1876
                and hasattr(e, 'errno')