~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-05-11 06:20:05 UTC
  • Revision ID: mbp@sourcefrog.net-20050511062005-297af3451635dae0
- Don't lose first line of command help!

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
 
19
 
import sys, os
 
19
import sys, os, time, os.path
 
20
from sets import Set
20
21
 
21
22
import bzrlib
22
 
from bzrlib.trace import mutter, note, log_error, warning
23
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
24
 
from bzrlib.branch import find_branch
25
 
from bzrlib import BZRDIR
26
 
 
27
 
 
28
 
plugin_cmds = {}
29
 
 
30
 
 
31
 
def register_command(cmd):
32
 
    "Utility function to help register a command"
33
 
    global plugin_cmds
34
 
    k = cmd.__name__
35
 
    if k.startswith("cmd_"):
36
 
        k_unsquished = _unsquish_command_name(k)
37
 
    else:
38
 
        k_unsquished = k
39
 
    if not plugin_cmds.has_key(k_unsquished):
40
 
        plugin_cmds[k_unsquished] = cmd
41
 
    else:
42
 
        log_error('Two plugins defined the same command: %r' % k)
43
 
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
23
from bzrlib.trace import mutter, note, log_error
 
24
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
25
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
 
26
from bzrlib.tree import RevisionTree, EmptyTree, Tree
 
27
from bzrlib.revision import Revision
 
28
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
 
29
     format_date
44
30
 
45
31
 
46
32
def _squish_command_name(cmd):
51
37
    assert cmd.startswith("cmd_")
52
38
    return cmd[4:].replace('_','-')
53
39
 
54
 
 
55
 
def _parse_revision_str(revstr):
56
 
    """This handles a revision string -> revno.
57
 
 
58
 
    This always returns a list.  The list will have one element for 
59
 
 
60
 
    It supports integers directly, but everything else it
61
 
    defers for passing to Branch.get_revision_info()
62
 
 
63
 
    >>> _parse_revision_str('234')
64
 
    [234]
65
 
    >>> _parse_revision_str('234..567')
66
 
    [234, 567]
67
 
    >>> _parse_revision_str('..')
68
 
    [None, None]
69
 
    >>> _parse_revision_str('..234')
70
 
    [None, 234]
71
 
    >>> _parse_revision_str('234..')
72
 
    [234, None]
73
 
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
74
 
    [234, 456, 789]
75
 
    >>> _parse_revision_str('234....789') # Error?
76
 
    [234, None, 789]
77
 
    >>> _parse_revision_str('revid:test@other.com-234234')
78
 
    ['revid:test@other.com-234234']
79
 
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
80
 
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
81
 
    >>> _parse_revision_str('revid:test@other.com-234234..23')
82
 
    ['revid:test@other.com-234234', 23]
83
 
    >>> _parse_revision_str('date:2005-04-12')
84
 
    ['date:2005-04-12']
85
 
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
86
 
    ['date:2005-04-12 12:24:33']
87
 
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
88
 
    ['date:2005-04-12T12:24:33']
89
 
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
90
 
    ['date:2005-04-12,12:24:33']
91
 
    >>> _parse_revision_str('-5..23')
92
 
    [-5, 23]
93
 
    >>> _parse_revision_str('-5')
94
 
    [-5]
95
 
    >>> _parse_revision_str('123a')
96
 
    ['123a']
97
 
    >>> _parse_revision_str('abc')
98
 
    ['abc']
99
 
    """
100
 
    import re
101
 
    old_format_re = re.compile('\d*:\d*')
102
 
    m = old_format_re.match(revstr)
103
 
    if m:
104
 
        warning('Colon separator for revision numbers is deprecated.'
105
 
                ' Use .. instead')
106
 
        revs = []
107
 
        for rev in revstr.split(':'):
108
 
            if rev:
109
 
                revs.append(int(rev))
110
 
            else:
111
 
                revs.append(None)
112
 
        return revs
113
 
    revs = []
114
 
    for x in revstr.split('..'):
115
 
        if not x:
116
 
            revs.append(None)
117
 
        else:
118
 
            try:
119
 
                revs.append(int(x))
120
 
            except ValueError:
121
 
                revs.append(x)
122
 
    return revs
123
 
 
124
 
 
125
 
 
126
 
def _get_cmd_dict(plugins_override=True):
127
 
    d = {}
 
40
def get_all_cmds():
 
41
    """Return canonical name and class for all registered commands."""
128
42
    for k, v in globals().iteritems():
129
43
        if k.startswith("cmd_"):
130
 
            d[_unsquish_command_name(k)] = v
131
 
    # If we didn't load plugins, the plugin_cmds dict will be empty
132
 
    if plugins_override:
133
 
        d.update(plugin_cmds)
134
 
    else:
135
 
        d2 = plugin_cmds.copy()
136
 
        d2.update(d)
137
 
        d = d2
138
 
    return d
139
 
 
140
 
    
141
 
def get_all_cmds(plugins_override=True):
142
 
    """Return canonical name and class for all registered commands."""
143
 
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
144
 
        yield k,v
145
 
 
146
 
 
147
 
def get_cmd_class(cmd, plugins_override=True):
 
44
            yield _unsquish_command_name(k), v
 
45
 
 
46
def get_cmd_class(cmd):
148
47
    """Return the canonical name and command class for a command.
149
48
    """
150
49
    cmd = str(cmd)                      # not unicode
151
50
 
152
51
    # first look up this command under the specified name
153
 
    cmds = _get_cmd_dict(plugins_override=plugins_override)
154
52
    try:
155
 
        return cmd, cmds[cmd]
 
53
        return cmd, globals()[_squish_command_name(cmd)]
156
54
    except KeyError:
157
55
        pass
158
56
 
159
57
    # look for any command which claims this as an alias
160
 
    for cmdname, cmdclass in cmds.iteritems():
 
58
    for cmdname, cmdclass in get_all_cmds():
161
59
        if cmd in cmdclass.aliases:
162
60
            return cmdname, cmdclass
163
61
 
168
66
    raise BzrCommandError("unknown command %r" % cmd)
169
67
 
170
68
 
171
 
class Command(object):
 
69
class Command:
172
70
    """Base class for commands.
173
71
 
174
72
    The docstring for an actual command should give a single-line
200
98
        assert isinstance(arguments, dict)
201
99
        cmdargs = options.copy()
202
100
        cmdargs.update(arguments)
203
 
        if self.__doc__ == Command.__doc__:
204
 
            from warnings import warn
205
 
            warn("No help message set for %r" % self)
 
101
        assert self.__doc__ != Command.__doc__, \
 
102
               ("No help message set for %r" % self)
206
103
        self.status = self.run(**cmdargs)
207
 
        if self.status is None:
208
 
            self.status = 0
209
104
 
210
105
    
211
106
    def run(self):
223
118
class ExternalCommand(Command):
224
119
    """Class to wrap external commands.
225
120
 
226
 
    We cheat a little here, when get_cmd_class() calls us we actually
227
 
    give it back an object we construct that has the appropriate path,
228
 
    help, options etc for the specified command.
229
 
 
230
 
    When run_bzr() tries to instantiate that 'class' it gets caught by
231
 
    the __call__ method, which we override to call the Command.__init__
232
 
    method. That then calls our run method which is pretty straight
233
 
    forward.
234
 
 
235
 
    The only wrinkle is that we have to map bzr's dictionary of options
236
 
    and arguments back into command line options and arguments for the
237
 
    script.
 
121
    We cheat a little here, when get_cmd_class() calls us we actually give it back
 
122
    an object we construct that has the appropriate path, help, options etc for the
 
123
    specified command.
 
124
 
 
125
    When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
 
126
    method, which we override to call the Command.__init__ method. That then calls
 
127
    our run method which is pretty straight forward.
 
128
 
 
129
    The only wrinkle is that we have to map bzr's dictionary of options and arguments
 
130
    back into command line options and arguments for the script.
238
131
    """
239
132
 
240
133
    def find_command(cls, cmd):
241
 
        import os.path
242
134
        bzrpath = os.environ.get('BZRPATH', '')
243
135
 
244
 
        for dir in bzrpath.split(os.pathsep):
 
136
        for dir in bzrpath.split(':'):
245
137
            path = os.path.join(dir, cmd)
246
138
            if os.path.isfile(path):
247
139
                return ExternalCommand(path)
253
145
    def __init__(self, path):
254
146
        self.path = path
255
147
 
 
148
        # TODO: If either of these fail, we should detect that and
 
149
        # assume that path is not really a bzr plugin after all.
 
150
 
256
151
        pipe = os.popen('%s --bzr-usage' % path, 'r')
257
152
        self.takes_options = pipe.readline().split()
258
 
 
259
 
        for opt in self.takes_options:
260
 
            if not opt in OPTIONS:
261
 
                raise BzrError("Unknown option '%s' returned by external command %s"
262
 
                               % (opt, path))
263
 
 
264
 
        # TODO: Is there any way to check takes_args is valid here?
265
153
        self.takes_args = pipe.readline().split()
266
 
 
267
 
        if pipe.close() is not None:
268
 
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
 
154
        pipe.close()
269
155
 
270
156
        pipe = os.popen('%s --bzr-help' % path, 'r')
271
157
        self.__doc__ = pipe.read()
272
 
        if pipe.close() is not None:
273
 
            raise BzrError("Failed funning '%s --bzr-help'" % path)
 
158
        pipe.close()
274
159
 
275
160
    def __call__(self, options, arguments):
276
161
        Command.__init__(self, options, arguments)
283
168
        keys = kargs.keys()
284
169
        keys.sort()
285
170
        for name in keys:
286
 
            optname = name.replace('_','-')
287
171
            value = kargs[name]
288
 
            if OPTIONS.has_key(optname):
 
172
            if OPTIONS.has_key(name):
289
173
                # it's an option
290
 
                opts.append('--%s' % optname)
 
174
                opts.append('--%s' % name)
291
175
                if value is not None and value is not True:
292
176
                    opts.append(str(value))
293
177
            else:
337
221
    directory is shown.  Otherwise, only the status of the specified
338
222
    files or directories is reported.  If a directory is given, status
339
223
    is reported for everything inside that directory.
340
 
 
341
 
    If a revision is specified, the changes since that revision are shown.
342
224
    """
343
225
    takes_args = ['file*']
344
 
    takes_options = ['all', 'show-ids', 'revision']
 
226
    takes_options = ['all', 'show-ids']
345
227
    aliases = ['st', 'stat']
346
228
    
347
229
    def run(self, all=False, show_ids=False, file_list=None):
348
230
        if file_list:
349
 
            b = find_branch(file_list[0])
 
231
            b = Branch(file_list[0], lock_mode='r')
350
232
            file_list = [b.relpath(x) for x in file_list]
351
233
            # special case: only one path was given and it's the root
352
234
            # of the branch
353
235
            if file_list == ['']:
354
236
                file_list = None
355
237
        else:
356
 
            b = find_branch('.')
357
 
            
358
 
        from bzrlib.status import show_status
359
 
        show_status(b, show_unchanged=all, show_ids=show_ids,
360
 
                    specific_files=file_list)
 
238
            b = Branch('.', lock_mode='r')
 
239
        import status
 
240
        status.show_status(b, show_unchanged=all, show_ids=show_ids,
 
241
                           file_list=file_list)
361
242
 
362
243
 
363
244
class cmd_cat_revision(Command):
367
248
    takes_args = ['revision_id']
368
249
    
369
250
    def run(self, revision_id):
370
 
        from bzrlib.xml import pack_xml
371
 
        pack_xml(find_branch('.').get_revision(revision_id), sys.stdout)
 
251
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
372
252
 
373
253
 
374
254
class cmd_revno(Command):
376
256
 
377
257
    This is equal to the number of revisions on this branch."""
378
258
    def run(self):
379
 
        print find_branch('.').revno()
380
 
 
381
 
class cmd_revision_info(Command):
382
 
    """Show revision number and revision id for a given revision identifier.
383
 
    """
384
 
    hidden = True
385
 
    takes_args = ['revision_info*']
386
 
    takes_options = ['revision']
387
 
    def run(self, revision=None, revision_info_list=None):
388
 
        from bzrlib.branch import find_branch
389
 
 
390
 
        revs = []
391
 
        if revision is not None:
392
 
            revs.extend(revision)
393
 
        if revision_info_list is not None:
394
 
            revs.extend(revision_info_list)
395
 
        if len(revs) == 0:
396
 
            raise BzrCommandError('You must supply a revision identifier')
397
 
 
398
 
        b = find_branch('.')
399
 
 
400
 
        for rev in revs:
401
 
            print '%4d %s' % b.get_revision_info(rev)
 
259
        print Branch('.').revno()
402
260
 
403
261
    
404
262
class cmd_add(Command):
414
272
    whether already versioned or not, are searched for files or
415
273
    subdirectories that are neither versioned or ignored, and these
416
274
    are added.  This search proceeds recursively into versioned
417
 
    directories.  If no names are given '.' is assumed.
 
275
    directories.
418
276
 
419
 
    Therefore simply saying 'bzr add' will version all files that
 
277
    Therefore simply saying 'bzr add .' will version all files that
420
278
    are currently unknown.
421
279
 
422
280
    TODO: Perhaps adding a file whose directly is not versioned should
423
281
    recursively add that parent, rather than giving an error?
424
282
    """
425
 
    takes_args = ['file*']
426
 
    takes_options = ['verbose', 'no-recurse']
 
283
    takes_args = ['file+']
 
284
    takes_options = ['verbose']
427
285
    
428
 
    def run(self, file_list, verbose=False, no_recurse=False):
429
 
        from bzrlib.add import smart_add
430
 
        smart_add(file_list, verbose, not no_recurse)
431
 
 
432
 
 
433
 
 
434
 
class cmd_mkdir(Command):
435
 
    """Create a new versioned directory.
436
 
 
437
 
    This is equivalent to creating the directory and then adding it.
438
 
    """
439
 
    takes_args = ['dir+']
440
 
 
441
 
    def run(self, dir_list):
442
 
        b = None
443
 
        
444
 
        for d in dir_list:
445
 
            os.mkdir(d)
446
 
            if not b:
447
 
                b = find_branch(d)
448
 
            b.add([d], verbose=True)
 
286
    def run(self, file_list, verbose=False):
 
287
        bzrlib.add.smart_add(file_list, verbose)
449
288
 
450
289
 
451
290
class cmd_relpath(Command):
452
291
    """Show path of a file relative to root"""
453
292
    takes_args = ['filename']
454
 
    hidden = True
455
293
    
456
294
    def run(self, filename):
457
 
        print find_branch(filename).relpath(filename)
 
295
        print Branch(filename).relpath(filename)
458
296
 
459
297
 
460
298
 
461
299
class cmd_inventory(Command):
462
300
    """Show inventory of the current working copy or a revision."""
463
 
    takes_options = ['revision', 'show-ids']
 
301
    takes_options = ['revision']
464
302
    
465
 
    def run(self, revision=None, show_ids=False):
466
 
        b = find_branch('.')
 
303
    def run(self, revision=None):
 
304
        b = Branch('.')
467
305
        if revision == None:
468
306
            inv = b.read_working_inventory()
469
307
        else:
470
 
            if len(revision) > 1:
471
 
                raise BzrCommandError('bzr inventory --revision takes'
472
 
                    ' exactly one revision identifier')
473
 
            inv = b.get_revision_inventory(b.lookup_revision(revision[0]))
 
308
            inv = b.get_revision_inventory(b.lookup_revision(revision))
474
309
 
475
 
        for path, entry in inv.entries():
476
 
            if show_ids:
477
 
                print '%-50s %s' % (path, entry.file_id)
478
 
            else:
479
 
                print path
 
310
        for path, entry in inv.iter_entries():
 
311
            print '%-50s %s' % (entry.file_id, path)
480
312
 
481
313
 
482
314
class cmd_move(Command):
489
321
    """
490
322
    takes_args = ['source$', 'dest']
491
323
    def run(self, source_list, dest):
492
 
        b = find_branch('.')
 
324
        b = Branch('.')
493
325
 
494
326
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
495
327
 
511
343
    takes_args = ['from_name', 'to_name']
512
344
    
513
345
    def run(self, from_name, to_name):
514
 
        b = find_branch('.')
 
346
        b = Branch('.')
515
347
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
516
348
 
517
349
 
518
350
 
519
 
 
520
 
 
521
 
class cmd_pull(Command):
522
 
    """Pull any changes from another branch into the current one.
523
 
 
524
 
    If the location is omitted, the last-used location will be used.
525
 
    Both the revision history and the working directory will be
526
 
    updated.
527
 
 
528
 
    This command only works on branches that have not diverged.  Branches are
529
 
    considered diverged if both branches have had commits without first
530
 
    pulling from the other.
531
 
 
532
 
    If branches have diverged, you can use 'bzr merge' to pull the text changes
533
 
    from one into the other.
534
 
    """
535
 
    takes_args = ['location?']
536
 
 
537
 
    def run(self, location=None):
538
 
        from bzrlib.merge import merge
539
 
        import tempfile
540
 
        from shutil import rmtree
541
 
        import errno
542
 
        
543
 
        br_to = find_branch('.')
544
 
        stored_loc = None
545
 
        try:
546
 
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
547
 
        except IOError, e:
548
 
            if e.errno != errno.ENOENT:
549
 
                raise
550
 
        if location is None:
551
 
            if stored_loc is None:
552
 
                raise BzrCommandError("No pull location known or specified.")
553
 
            else:
554
 
                print "Using last location: %s" % stored_loc
555
 
                location = stored_loc
556
 
        cache_root = tempfile.mkdtemp()
557
 
        from bzrlib.branch import DivergedBranches
558
 
        br_from = find_branch(location)
559
 
        location = pull_loc(br_from)
560
 
        old_revno = br_to.revno()
561
 
        try:
562
 
            from branch import find_cached_branch, DivergedBranches
563
 
            br_from = find_cached_branch(location, cache_root)
564
 
            location = pull_loc(br_from)
565
 
            old_revno = br_to.revno()
566
 
            try:
567
 
                br_to.update_revisions(br_from)
568
 
            except DivergedBranches:
569
 
                raise BzrCommandError("These branches have diverged."
570
 
                    "  Try merge.")
571
 
                
572
 
            merge(('.', -1), ('.', old_revno), check_clean=False)
573
 
            if location != stored_loc:
574
 
                br_to.controlfile("x-pull", "wb").write(location + "\n")
575
 
        finally:
576
 
            rmtree(cache_root)
577
 
 
578
 
 
579
 
 
580
 
class cmd_branch(Command):
581
 
    """Create a new copy of a branch.
582
 
 
583
 
    If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
584
 
    be used.  In other words, "branch ../foo/bar" will attempt to create ./bar.
585
 
 
586
 
    To retrieve the branch as of a particular revision, supply the --revision
587
 
    parameter, as in "branch foo/bar -r 5".
588
 
    """
589
 
    takes_args = ['from_location', 'to_location?']
590
 
    takes_options = ['revision']
591
 
 
592
 
    def run(self, from_location, to_location=None, revision=None):
593
 
        import errno
594
 
        from bzrlib.merge import merge
595
 
        from bzrlib.branch import DivergedBranches, NoSuchRevision, \
596
 
             find_cached_branch, Branch
597
 
        from shutil import rmtree
598
 
        from meta_store import CachedStore
599
 
        import tempfile
600
 
        cache_root = tempfile.mkdtemp()
601
 
 
602
 
        if revision is None:
603
 
            revision = [None]
604
 
        elif len(revision) > 1:
605
 
            raise BzrCommandError('bzr branch --revision takes exactly 1 revision value')
606
 
 
607
 
        try:
608
 
            try:
609
 
                br_from = find_cached_branch(from_location, cache_root)
610
 
            except OSError, e:
611
 
                if e.errno == errno.ENOENT:
612
 
                    raise BzrCommandError('Source location "%s" does not'
613
 
                                          ' exist.' % to_location)
614
 
                else:
615
 
                    raise
616
 
 
617
 
            if to_location is None:
618
 
                to_location = os.path.basename(from_location.rstrip("/\\"))
619
 
 
620
 
            try:
621
 
                os.mkdir(to_location)
622
 
            except OSError, e:
623
 
                if e.errno == errno.EEXIST:
624
 
                    raise BzrCommandError('Target directory "%s" already'
625
 
                                          ' exists.' % to_location)
626
 
                if e.errno == errno.ENOENT:
627
 
                    raise BzrCommandError('Parent of "%s" does not exist.' %
628
 
                                          to_location)
629
 
                else:
630
 
                    raise
631
 
            br_to = Branch(to_location, init=True)
632
 
 
633
 
            br_to.set_root_id(br_from.get_root_id())
634
 
 
635
 
            if revision:
636
 
                if revision[0] is None:
637
 
                    revno = br_from.revno()
638
 
                else:
639
 
                    revno, rev_id = br_from.get_revision_info(revision[0])
640
 
                try:
641
 
                    br_to.update_revisions(br_from, stop_revision=revno)
642
 
                except NoSuchRevision:
643
 
                    rmtree(to_location)
644
 
                    msg = "The branch %s has no revision %d." % (from_location,
645
 
                                                                 revno)
646
 
                    raise BzrCommandError(msg)
647
 
            
648
 
            merge((to_location, -1), (to_location, 0), this_dir=to_location,
649
 
                  check_clean=False, ignore_zero=True)
650
 
            from_location = pull_loc(br_from)
651
 
            br_to.controlfile("x-pull", "wb").write(from_location + "\n")
652
 
        finally:
653
 
            rmtree(cache_root)
654
 
 
655
 
 
656
 
def pull_loc(branch):
657
 
    # TODO: Should perhaps just make attribute be 'base' in
658
 
    # RemoteBranch and Branch?
659
 
    if hasattr(branch, "baseurl"):
660
 
        return branch.baseurl
661
 
    else:
662
 
        return branch.base
663
 
 
664
 
 
665
 
 
666
351
class cmd_renames(Command):
667
352
    """Show list of renamed files.
668
353
 
673
358
    takes_args = ['dir?']
674
359
 
675
360
    def run(self, dir='.'):
676
 
        b = find_branch(dir)
 
361
        b = Branch(dir)
677
362
        old_inv = b.basis_tree().inventory
678
363
        new_inv = b.read_working_inventory()
679
364
 
690
375
    def run(self, branch=None):
691
376
        import info
692
377
 
 
378
        from branch import find_branch
693
379
        b = find_branch(branch)
694
380
        info.show_info(b)
695
381
 
704
390
    takes_options = ['verbose']
705
391
    
706
392
    def run(self, file_list, verbose=False):
707
 
        b = find_branch(file_list[0])
 
393
        b = Branch(file_list[0])
708
394
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
709
395
 
710
396
 
718
404
    hidden = True
719
405
    takes_args = ['filename']
720
406
    def run(self, filename):
721
 
        b = find_branch(filename)
 
407
        b = Branch(filename)
722
408
        i = b.inventory.path2id(b.relpath(filename))
723
409
        if i == None:
724
 
            raise BzrError("%r is not a versioned file" % filename)
 
410
            bailout("%r is not a versioned file" % filename)
725
411
        else:
726
412
            print i
727
413
 
734
420
    hidden = True
735
421
    takes_args = ['filename']
736
422
    def run(self, filename):
737
 
        b = find_branch(filename)
 
423
        b = Branch(filename)
738
424
        inv = b.inventory
739
425
        fid = inv.path2id(b.relpath(filename))
740
426
        if fid == None:
741
 
            raise BzrError("%r is not a versioned file" % filename)
 
427
            bailout("%r is not a versioned file" % filename)
742
428
        for fip in inv.get_idpath(fid):
743
429
            print fip
744
430
 
745
431
 
746
432
class cmd_revision_history(Command):
747
433
    """Display list of revision ids on this branch."""
748
 
    hidden = True
749
434
    def run(self):
750
 
        for patchid in find_branch('.').revision_history():
 
435
        for patchid in Branch('.').revision_history():
751
436
            print patchid
752
437
 
753
438
 
754
439
class cmd_directories(Command):
755
440
    """Display list of versioned directories in this branch."""
756
441
    def run(self):
757
 
        for name, ie in find_branch('.').read_working_inventory().directories():
 
442
        for name, ie in Branch('.').read_working_inventory().directories():
758
443
            if name == '':
759
444
                print '.'
760
445
            else:
775
460
        bzr commit -m 'imported project'
776
461
    """
777
462
    def run(self):
778
 
        from bzrlib.branch import Branch
779
463
        Branch('.', init=True)
780
464
 
781
465
 
804
488
    """
805
489
    
806
490
    takes_args = ['file*']
807
 
    takes_options = ['revision', 'diff-options']
808
 
    aliases = ['di', 'dif']
 
491
    takes_options = ['revision']
 
492
    aliases = ['di']
809
493
 
810
 
    def run(self, revision=None, file_list=None, diff_options=None):
 
494
    def run(self, revision=None, file_list=None):
811
495
        from bzrlib.diff import show_diff
812
 
 
813
 
        if file_list:
814
 
            b = find_branch(file_list[0])
815
 
            file_list = [b.relpath(f) for f in file_list]
816
 
            if file_list == ['']:
817
 
                # just pointing to top-of-tree
818
 
                file_list = None
819
 
        else:
820
 
            b = find_branch('.')
821
 
 
822
 
        # TODO: Make show_diff support taking 2 arguments
823
 
        base_rev = None
824
 
        if revision is not None:
825
 
            if len(revision) != 1:
826
 
                raise BzrCommandError('bzr diff --revision takes exactly one revision identifier')
827
 
            base_rev = revision[0]
828
496
    
829
 
        show_diff(b, base_rev, specific_files=file_list,
830
 
                  external_diff_options=diff_options)
 
497
        show_diff(Branch('.'), revision, file_list)
831
498
 
832
499
 
833
500
        
839
506
    TODO: Show files deleted since a previous revision, or between two revisions.
840
507
    """
841
508
    def run(self, show_ids=False):
842
 
        b = find_branch('.')
 
509
        b = Branch('.')
843
510
        old = b.basis_tree()
844
511
        new = b.working_tree()
845
512
 
860
527
    """List files modified in working tree."""
861
528
    hidden = True
862
529
    def run(self):
863
 
        from bzrlib.delta import compare_trees
864
 
 
865
 
        b = find_branch('.')
866
 
        td = compare_trees(b.basis_tree(), b.working_tree())
867
 
 
868
 
        for path, id, kind in td.modified:
869
 
            print path
 
530
        import statcache
 
531
        b = Branch('.')
 
532
        inv = b.read_working_inventory()
 
533
        sc = statcache.update_cache(b, inv)
 
534
        basis = b.basis_tree()
 
535
        basis_inv = basis.inventory
 
536
        
 
537
        # We used to do this through iter_entries(), but that's slow
 
538
        # when most of the files are unmodified, as is usually the
 
539
        # case.  So instead we iterate by inventory entry, and only
 
540
        # calculate paths as necessary.
 
541
 
 
542
        for file_id in basis_inv:
 
543
            cacheentry = sc.get(file_id)
 
544
            if not cacheentry:                 # deleted
 
545
                continue
 
546
            ie = basis_inv[file_id]
 
547
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
 
548
                path = inv.id2path(file_id)
 
549
                print path
870
550
 
871
551
 
872
552
 
874
554
    """List files added in working tree."""
875
555
    hidden = True
876
556
    def run(self):
877
 
        b = find_branch('.')
 
557
        b = Branch('.')
878
558
        wt = b.working_tree()
879
559
        basis_inv = b.basis_tree().inventory
880
560
        inv = wt.inventory
896
576
    takes_args = ['filename?']
897
577
    def run(self, filename=None):
898
578
        """Print the branch root."""
 
579
        from branch import find_branch
899
580
        b = find_branch(filename)
900
581
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
901
582
 
903
584
class cmd_log(Command):
904
585
    """Show log of this branch.
905
586
 
906
 
    To request a range of logs, you can use the command -r begin:end
907
 
    -r revision requests a specific revision, -r :end or -r begin: are
908
 
    also valid.
909
 
 
910
 
    --message allows you to give a regular expression, which will be evaluated
911
 
    so that only matching entries will be displayed.
912
 
 
913
 
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
914
 
  
 
587
    TODO: Option to limit range.
 
588
 
 
589
    TODO: Perhaps show most-recent first with an option for last.
915
590
    """
916
 
 
917
591
    takes_args = ['filename?']
918
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision','long', 'message']
919
 
    
920
 
    def run(self, filename=None, timezone='original',
921
 
            verbose=False,
922
 
            show_ids=False,
923
 
            forward=False,
924
 
            revision=None,
925
 
            message=None,
926
 
            long=False):
927
 
        from bzrlib.branch import find_branch
928
 
        from bzrlib.log import log_formatter, show_log
929
 
        import codecs
930
 
 
931
 
        direction = (forward and 'forward') or 'reverse'
932
 
        
 
592
    takes_options = ['timezone', 'verbose', 'show-ids']
 
593
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
 
594
        from branch import find_branch
 
595
        b = find_branch((filename or '.'), lock_mode='r')
933
596
        if filename:
934
 
            b = find_branch(filename)
935
 
            fp = b.relpath(filename)
936
 
            if fp:
937
 
                file_id = b.read_working_inventory().path2id(fp)
938
 
            else:
939
 
                file_id = None  # points to branch root
940
 
        else:
941
 
            b = find_branch('.')
942
 
            file_id = None
943
 
 
944
 
        if revision is None:
945
 
            rev1 = None
946
 
            rev2 = None
947
 
        elif len(revision) == 1:
948
 
            rev1 = rev2 = b.get_revision_info(revision[0])[0]
949
 
        elif len(revision) == 2:
950
 
            rev1 = b.get_revision_info(revision[0])[0]
951
 
            rev2 = b.get_revision_info(revision[1])[0]
952
 
        else:
953
 
            raise BzrCommandError('bzr log --revision takes one or two values.')
954
 
 
955
 
        if rev1 == 0:
956
 
            rev1 = None
957
 
        if rev2 == 0:
958
 
            rev2 = None
959
 
 
960
 
        mutter('encoding log as %r' % bzrlib.user_encoding)
961
 
 
962
 
        # use 'replace' so that we don't abort if trying to write out
963
 
        # in e.g. the default C locale.
964
 
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
965
 
 
966
 
        if long:
967
 
            log_format = 'long'
968
 
        else:
969
 
            log_format = 'short'
970
 
        lf = log_formatter(log_format,
971
 
                           show_ids=show_ids,
972
 
                           to_file=outf,
973
 
                           show_timezone=timezone)
974
 
 
975
 
        show_log(b,
976
 
                 lf,
977
 
                 file_id,
978
 
                 verbose=verbose,
979
 
                 direction=direction,
980
 
                 start_revision=rev1,
981
 
                 end_revision=rev2,
982
 
                 search=message)
 
597
            filename = b.relpath(filename)
 
598
        bzrlib.show_log(b, filename,
 
599
                        show_timezone=timezone,
 
600
                        verbose=verbose,
 
601
                        show_ids=show_ids)
983
602
 
984
603
 
985
604
 
986
605
class cmd_touching_revisions(Command):
987
 
    """Return revision-ids which affected a particular file.
988
 
 
989
 
    A more user-friendly interface is "bzr log FILE"."""
 
606
    """Return revision-ids which affected a particular file."""
990
607
    hidden = True
991
608
    takes_args = ["filename"]
992
609
    def run(self, filename):
993
 
        b = find_branch(filename)
 
610
        b = Branch(filename, lock_mode='r')
994
611
        inv = b.read_working_inventory()
995
612
        file_id = inv.path2id(b.relpath(filename))
996
613
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
1004
621
    """
1005
622
    hidden = True
1006
623
    def run(self, revision=None, verbose=False):
1007
 
        b = find_branch('.')
 
624
        b = Branch('.')
1008
625
        if revision == None:
1009
626
            tree = b.working_tree()
1010
627
        else:
1026
643
 
1027
644
 
1028
645
class cmd_unknowns(Command):
1029
 
    """List unknown files."""
 
646
    """List unknown files"""
1030
647
    def run(self):
1031
 
        from bzrlib.osutils import quotefn
1032
 
        for f in find_branch('.').unknowns():
 
648
        for f in Branch('.').unknowns():
1033
649
            print quotefn(f)
1034
650
 
1035
651
 
1036
652
 
1037
653
class cmd_ignore(Command):
1038
 
    """Ignore a command or pattern.
 
654
    """Ignore a command or pattern
1039
655
 
1040
656
    To remove patterns from the ignore list, edit the .bzrignore file.
1041
657
 
1055
671
    
1056
672
    def run(self, name_pattern):
1057
673
        from bzrlib.atomicfile import AtomicFile
1058
 
        import os.path
 
674
        import codecs
1059
675
 
1060
 
        b = find_branch('.')
 
676
        b = Branch('.')
1061
677
        ifn = b.abspath('.bzrignore')
1062
678
 
 
679
        # FIXME: probably doesn't handle non-ascii patterns
 
680
 
1063
681
        if os.path.exists(ifn):
1064
 
            f = open(ifn, 'rt')
1065
 
            try:
1066
 
                igns = f.read().decode('utf-8')
1067
 
            finally:
1068
 
                f.close()
 
682
            f = b.controlfile(ifn, 'rt')
 
683
            igns = f.read()
 
684
            f.close()
1069
685
        else:
1070
686
            igns = ''
1071
687
 
1072
 
        # TODO: If the file already uses crlf-style termination, maybe
1073
 
        # we should use that for the newly added lines?
1074
 
 
1075
688
        if igns and igns[-1] != '\n':
1076
689
            igns += '\n'
1077
690
        igns += name_pattern + '\n'
1078
691
 
1079
 
        try:
1080
 
            f = AtomicFile(ifn, 'wt')
1081
 
            f.write(igns.encode('utf-8'))
1082
 
            f.commit()
1083
 
        finally:
1084
 
            f.close()
 
692
        f = AtomicFile(ifn, 'wt')
 
693
        f.write(igns)
 
694
        f.commit()
1085
695
 
1086
696
        inv = b.working_tree().inventory
1087
697
        if inv.path2id('.bzrignore'):
1097
707
 
1098
708
    See also: bzr ignore"""
1099
709
    def run(self):
1100
 
        tree = find_branch('.').working_tree()
 
710
        tree = Branch('.').working_tree()
1101
711
        for path, file_class, kind, file_id in tree.list_files():
1102
712
            if file_class != 'I':
1103
713
                continue
1121
731
        except ValueError:
1122
732
            raise BzrCommandError("not a valid revision-number: %r" % revno)
1123
733
 
1124
 
        print find_branch('.').lookup_revision(revno)
 
734
        print Branch('.').lookup_revision(revno)
1125
735
 
1126
736
 
1127
737
class cmd_export(Command):
1128
738
    """Export past revision to destination directory.
1129
739
 
1130
 
    If no revision is specified this exports the last committed revision.
1131
 
 
1132
 
    Format may be an "exporter" name, such as tar, tgz, tbz2.  If none is
1133
 
    given, try to find the format with the extension. If no extension
1134
 
    is found exports to a directory (equivalent to --format=dir).
1135
 
 
1136
 
    Root may be the top directory for tar, tgz and tbz2 formats. If none
1137
 
    is given, the top directory will be the root name of the file."""
1138
 
    # TODO: list known exporters
 
740
    If no revision is specified this exports the last committed revision."""
1139
741
    takes_args = ['dest']
1140
 
    takes_options = ['revision', 'format', 'root']
1141
 
    def run(self, dest, revision=None, format=None, root=None):
1142
 
        import os.path
1143
 
        b = find_branch('.')
1144
 
        if revision is None:
1145
 
            rev_id = b.last_patch()
 
742
    takes_options = ['revision']
 
743
    def run(self, dest, revision=None):
 
744
        b = Branch('.')
 
745
        if revision == None:
 
746
            rh = b.revision_history()[-1]
1146
747
        else:
1147
 
            if len(revision) != 1:
1148
 
                raise BzrError('bzr export --revision takes exactly 1 argument')
1149
 
            revno, rev_id = b.get_revision_info(revision[0])
1150
 
        t = b.revision_tree(rev_id)
1151
 
        root, ext = os.path.splitext(dest)
1152
 
        if not format:
1153
 
            if ext in (".tar",):
1154
 
                format = "tar"
1155
 
            elif ext in (".gz", ".tgz"):
1156
 
                format = "tgz"
1157
 
            elif ext in (".bz2", ".tbz2"):
1158
 
                format = "tbz2"
1159
 
            else:
1160
 
                format = "dir"
1161
 
        t.export(dest, format, root)
 
748
            rh = b.lookup_revision(int(revision))
 
749
        t = b.revision_tree(rh)
 
750
        t.export(dest)
1162
751
 
1163
752
 
1164
753
class cmd_cat(Command):
1170
759
    def run(self, filename, revision=None):
1171
760
        if revision == None:
1172
761
            raise BzrCommandError("bzr cat requires a revision number")
1173
 
        elif len(revision) != 1:
1174
 
            raise BzrCommandError("bzr cat --revision takes exactly one number")
1175
 
        b = find_branch('.')
1176
 
        b.print_file(b.relpath(filename), revision[0])
 
762
        b = Branch('.')
 
763
        b.print_file(b.relpath(filename), int(revision))
1177
764
 
1178
765
 
1179
766
class cmd_local_time_offset(Command):
1186
773
 
1187
774
class cmd_commit(Command):
1188
775
    """Commit changes into a new revision.
1189
 
    
1190
 
    If no arguments are given, the entire tree is committed.
1191
 
 
1192
 
    If selected files are specified, only changes to those files are
1193
 
    committed.  If a directory is specified then the directory and everything 
1194
 
    within it is committed.
1195
 
 
1196
 
    A selected-file commit may fail in some cases where the committed
1197
 
    tree would be invalid, such as trying to commit a file in a
1198
 
    newly-added directory that is not itself committed.
 
776
 
 
777
    TODO: Commit only selected files.
1199
778
 
1200
779
    TODO: Run hooks on tree to-be-committed, and after commit.
1201
780
 
1202
781
    TODO: Strict commit that fails if there are unknown or deleted files.
1203
782
    """
1204
 
    takes_args = ['selected*']
1205
 
    takes_options = ['message', 'file', 'verbose', 'unchanged']
 
783
    takes_options = ['message', 'file', 'verbose']
1206
784
    aliases = ['ci', 'checkin']
1207
785
 
1208
 
    # TODO: Give better message for -s, --summary, used by tla people
1209
 
    
1210
 
    def run(self, message=None, file=None, verbose=True, selected_list=None,
1211
 
            unchanged=False):
1212
 
        from bzrlib.errors import PointlessCommit
1213
 
        from bzrlib.osutils import get_text_message
1214
 
 
 
786
    def run(self, message=None, file=None, verbose=False):
1215
787
        ## Warning: shadows builtin file()
1216
788
        if not message and not file:
1217
 
            # FIXME: Ugly; change status code to send to a provided function?
1218
 
            
1219
 
            import cStringIO
1220
 
            stdout = sys.stdout
1221
 
            catcher = cStringIO.StringIO()
1222
 
            sys.stdout = catcher
1223
 
            cmd_status({"file_list":selected_list}, {})
1224
 
            info = catcher.getvalue()
1225
 
            sys.stdout = stdout
1226
 
            message = get_text_message(info)
1227
 
            
1228
 
            if message is None:
1229
 
                raise BzrCommandError("please specify a commit message",
1230
 
                                      ["use either --message or --file"])
 
789
            raise BzrCommandError("please specify a commit message",
 
790
                                  ["use either --message or --file"])
1231
791
        elif message and file:
1232
792
            raise BzrCommandError("please specify either --message or --file")
1233
793
        
1235
795
            import codecs
1236
796
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1237
797
 
1238
 
        b = find_branch('.')
1239
 
        if selected_list:
1240
 
            selected_list = [b.relpath(s) for s in selected_list]
1241
 
            
1242
 
        try:
1243
 
            b.commit(message, verbose=verbose,
1244
 
                     specific_files=selected_list,
1245
 
                     allow_pointless=unchanged)
1246
 
        except PointlessCommit:
1247
 
            # FIXME: This should really happen before the file is read in;
1248
 
            # perhaps prepare the commit; get the message; then actually commit
1249
 
            raise BzrCommandError("no changes to commit",
1250
 
                                  ["use --unchanged to commit anyhow"])
 
798
        Branch('.').commit(message, verbose=verbose)
1251
799
 
1252
800
 
1253
801
class cmd_check(Command):
1255
803
 
1256
804
    This command checks various invariants about the branch storage to
1257
805
    detect data corruption or bzr bugs.
1258
 
 
1259
 
    If given the --update flag, it will update some optional fields
1260
 
    to help ensure data consistency.
1261
 
    """
1262
 
    takes_args = ['dir?']
1263
 
 
1264
 
    def run(self, dir='.'):
1265
 
        from bzrlib.check import check
1266
 
        check(find_branch(dir))
1267
 
 
1268
 
 
1269
 
 
1270
 
class cmd_scan_cache(Command):
1271
 
    hidden = True
1272
 
    def run(self):
1273
 
        from bzrlib.hashcache import HashCache
1274
 
        import os
1275
 
 
1276
 
        c = HashCache('.')
1277
 
        c.read()
1278
 
        c.scan()
1279
 
            
1280
 
        print '%6d stats' % c.stat_count
1281
 
        print '%6d in hashcache' % len(c._cache)
1282
 
        print '%6d files removed from cache' % c.removed_count
1283
 
        print '%6d hashes updated' % c.update_count
1284
 
        print '%6d files changed too recently to cache' % c.danger_count
1285
 
 
1286
 
        if c.needs_write:
1287
 
            c.write()
1288
 
            
1289
 
 
1290
 
 
1291
 
class cmd_upgrade(Command):
1292
 
    """Upgrade branch storage to current format.
1293
 
 
1294
 
    This should normally be used only after the check command tells
1295
 
    you to run it.
1296
 
    """
1297
 
    takes_args = ['dir?']
1298
 
 
1299
 
    def run(self, dir='.'):
1300
 
        from bzrlib.upgrade import upgrade
1301
 
        upgrade(find_branch(dir))
 
806
    """
 
807
    takes_args = ['dir?']
 
808
    def run(self, dir='.'):
 
809
        import bzrlib.check
 
810
        bzrlib.check.check(Branch(dir, find_root=False))
1302
811
 
1303
812
 
1304
813
 
1316
825
class cmd_selftest(Command):
1317
826
    """Run internal test suite"""
1318
827
    hidden = True
1319
 
    takes_options = ['verbose']
1320
 
    def run(self, verbose=False):
1321
 
        from bzrlib.selftest import selftest
1322
 
        return int(not selftest(verbose=verbose))
 
828
    def run(self):
 
829
        failures, tests = 0, 0
 
830
 
 
831
        import doctest, bzrlib.store, bzrlib.tests
 
832
        bzrlib.trace.verbose = False
 
833
 
 
834
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
 
835
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
 
836
            mf, mt = doctest.testmod(m)
 
837
            failures += mf
 
838
            tests += mt
 
839
            print '%-40s %3d tests' % (m.__name__, mt),
 
840
            if mf:
 
841
                print '%3d FAILED!' % mf
 
842
            else:
 
843
                print
 
844
 
 
845
        print '%-40s %3d tests' % ('total', tests),
 
846
        if failures:
 
847
            print '%3d FAILED!' % failures
 
848
        else:
 
849
            print
 
850
 
1323
851
 
1324
852
 
1325
853
class cmd_version(Command):
1326
 
    """Show version of bzr."""
 
854
    """Show version of bzr"""
1327
855
    def run(self):
1328
856
        show_version()
1329
857
 
1330
858
def show_version():
1331
859
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
1332
 
    # is bzrlib itself in a branch?
1333
 
    bzrrev = bzrlib.get_bzr_revision()
1334
 
    if bzrrev:
1335
 
        print "  (bzr checkout, revision %d {%s})" % bzrrev
1336
860
    print bzrlib.__copyright__
1337
861
    print "http://bazaar-ng.org/"
1338
862
    print
1347
871
    def run(self):
1348
872
        print "it sure does!"
1349
873
 
1350
 
def parse_spec(spec):
1351
 
    """
1352
 
    >>> parse_spec(None)
1353
 
    [None, None]
1354
 
    >>> parse_spec("./")
1355
 
    ['./', None]
1356
 
    >>> parse_spec("../@")
1357
 
    ['..', -1]
1358
 
    >>> parse_spec("../f/@35")
1359
 
    ['../f', 35]
1360
 
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
1361
 
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
1362
 
    """
1363
 
    if spec is None:
1364
 
        return [None, None]
1365
 
    if '/@' in spec:
1366
 
        parsed = spec.split('/@')
1367
 
        assert len(parsed) == 2
1368
 
        if parsed[1] == "":
1369
 
            parsed[1] = -1
1370
 
        else:
1371
 
            try:
1372
 
                parsed[1] = int(parsed[1])
1373
 
            except ValueError:
1374
 
                pass # We can allow stuff like ./@revid:blahblahblah
1375
 
            else:
1376
 
                assert parsed[1] >=0
1377
 
    else:
1378
 
        parsed = [spec, None]
1379
 
    return parsed
1380
 
 
1381
 
 
1382
 
 
1383
 
class cmd_merge(Command):
1384
 
    """Perform a three-way merge of trees.
1385
 
    
1386
 
    The SPEC parameters are working tree or revision specifiers.  Working trees
1387
 
    are specified using standard paths or urls.  No component of a directory
1388
 
    path may begin with '@'.
1389
 
    
1390
 
    Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
1391
 
 
1392
 
    Revisions are specified using a dirname/@revno pair, where dirname is the
1393
 
    branch directory and revno is the revision within that branch.  If no revno
1394
 
    is specified, the latest revision is used.
1395
 
 
1396
 
    Revision examples: './@127', 'foo/@', '../@1'
1397
 
 
1398
 
    The OTHER_SPEC parameter is required.  If the BASE_SPEC parameter is
1399
 
    not supplied, the common ancestor of OTHER_SPEC the current branch is used
1400
 
    as the BASE.
1401
 
 
1402
 
    merge refuses to run if there are any uncommitted changes, unless
1403
 
    --force is given.
1404
 
    """
1405
 
    takes_args = ['other_spec', 'base_spec?']
1406
 
    takes_options = ['force']
1407
 
 
1408
 
    def run(self, other_spec, base_spec=None, force=False):
1409
 
        from bzrlib.merge import merge
1410
 
        merge(parse_spec(other_spec), parse_spec(base_spec),
1411
 
              check_clean=(not force))
1412
 
 
1413
 
 
1414
 
 
1415
 
class cmd_revert(Command):
1416
 
    """Restore selected files from a previous revision.
1417
 
    """
1418
 
    takes_args = ['file+']
1419
 
    def run(self, file_list):
1420
 
        from bzrlib.branch import find_branch
1421
 
        
1422
 
        if not file_list:
1423
 
            file_list = ['.']
1424
 
            
1425
 
        b = find_branch(file_list[0])
1426
 
 
1427
 
        b.revert([b.relpath(f) for f in file_list])
1428
 
 
1429
 
 
1430
 
class cmd_merge_revert(Command):
1431
 
    """Reverse all changes since the last commit.
1432
 
 
1433
 
    Only versioned files are affected.
1434
 
 
1435
 
    TODO: Store backups of any files that will be reverted, so
1436
 
          that the revert can be undone.          
1437
 
    """
1438
 
    takes_options = ['revision']
1439
 
 
1440
 
    def run(self, revision=None):
1441
 
        from bzrlib.merge import merge
1442
 
        if revision is None:
1443
 
            revision = [-1]
1444
 
        elif len(revision) != 1:
1445
 
            raise BzrCommandError('bzr merge-revert --revision takes exactly 1 argument')
1446
 
        merge(('.', revision[0]), parse_spec('.'),
1447
 
              check_clean=False,
1448
 
              ignore_zero=True)
1449
 
 
1450
874
 
1451
875
class cmd_assert_fail(Command):
1452
876
    """Test reporting of assertion failures"""
1467
891
        help.help(topic)
1468
892
 
1469
893
 
1470
 
 
1471
 
 
1472
 
class cmd_plugins(Command):
1473
 
    """List plugins"""
 
894
class cmd_update_stat_cache(Command):
 
895
    """Update stat-cache mapping inodes to SHA-1 hashes.
 
896
 
 
897
    For testing only."""
1474
898
    hidden = True
1475
899
    def run(self):
1476
 
        import bzrlib.plugin
1477
 
        from inspect import getdoc
1478
 
        from pprint import pprint
1479
 
        for plugin in bzrlib.plugin.all_plugins:
1480
 
            print plugin.__path__[0]
1481
 
            d = getdoc(plugin)
1482
 
            if d:
1483
 
                print '\t', d.split('\n')[0]
1484
 
 
1485
 
        #pprint(bzrlib.plugin.all_plugins)
1486
 
 
 
900
        import statcache
 
901
        b = Branch('.')
 
902
        statcache.update_cache(b.base, b.read_working_inventory())
 
903
 
 
904
 
 
905
######################################################################
 
906
# main routine
1487
907
 
1488
908
 
1489
909
# list of all available options; the rhs can be either None for an
1491
911
# the type.
1492
912
OPTIONS = {
1493
913
    'all':                    None,
1494
 
    'diff-options':           str,
1495
914
    'help':                   None,
1496
915
    'file':                   unicode,
1497
 
    'force':                  None,
1498
 
    'format':                 unicode,
1499
 
    'forward':                None,
1500
916
    'message':                unicode,
1501
 
    'no-recurse':             None,
1502
917
    'profile':                None,
1503
 
    'revision':               _parse_revision_str,
 
918
    'revision':               int,
1504
919
    'show-ids':               None,
1505
920
    'timezone':               str,
1506
921
    'verbose':                None,
1507
922
    'version':                None,
1508
923
    'email':                  None,
1509
 
    'unchanged':              None,
1510
 
    'update':                 None,
1511
 
    'long':                   None,
1512
 
    'root':                   str,
1513
924
    }
1514
925
 
1515
926
SHORT_OPTIONS = {
 
927
    'm':                      'message',
1516
928
    'F':                      'file', 
1517
 
    'h':                      'help',
1518
 
    'm':                      'message',
1519
929
    'r':                      'revision',
1520
930
    'v':                      'verbose',
1521
 
    'l':                      'long',
1522
931
}
1523
932
 
1524
933
 
1538
947
    (['status'], {'all': True})
1539
948
    >>> parse_args('commit --message=biter'.split())
1540
949
    (['commit'], {'message': u'biter'})
1541
 
    >>> parse_args('log -r 500'.split())
1542
 
    (['log'], {'revision': [500]})
1543
 
    >>> parse_args('log -r500..600'.split())
1544
 
    (['log'], {'revision': [500, 600]})
1545
 
    >>> parse_args('log -vr500..600'.split())
1546
 
    (['log'], {'verbose': True, 'revision': [500, 600]})
1547
 
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
1548
 
    (['log'], {'revision': ['v500', 600]})
1549
950
    """
1550
951
    args = []
1551
952
    opts = {}
1565
966
                else:
1566
967
                    optname = a[2:]
1567
968
                if optname not in OPTIONS:
1568
 
                    raise BzrError('unknown long option %r' % a)
 
969
                    bailout('unknown long option %r' % a)
1569
970
            else:
1570
971
                shortopt = a[1:]
1571
 
                if shortopt in SHORT_OPTIONS:
1572
 
                    # Multi-character options must have a space to delimit
1573
 
                    # their value
1574
 
                    optname = SHORT_OPTIONS[shortopt]
1575
 
                else:
1576
 
                    # Single character short options, can be chained,
1577
 
                    # and have their value appended to their name
1578
 
                    shortopt = a[1:2]
1579
 
                    if shortopt not in SHORT_OPTIONS:
1580
 
                        # We didn't find the multi-character name, and we
1581
 
                        # didn't find the single char name
1582
 
                        raise BzrError('unknown short option %r' % a)
1583
 
                    optname = SHORT_OPTIONS[shortopt]
1584
 
 
1585
 
                    if a[2:]:
1586
 
                        # There are extra things on this option
1587
 
                        # see if it is the value, or if it is another
1588
 
                        # short option
1589
 
                        optargfn = OPTIONS[optname]
1590
 
                        if optargfn is None:
1591
 
                            # This option does not take an argument, so the
1592
 
                            # next entry is another short option, pack it back
1593
 
                            # into the list
1594
 
                            argv.insert(0, '-' + a[2:])
1595
 
                        else:
1596
 
                            # This option takes an argument, so pack it
1597
 
                            # into the array
1598
 
                            optarg = a[2:]
 
972
                if shortopt not in SHORT_OPTIONS:
 
973
                    bailout('unknown short option %r' % a)
 
974
                optname = SHORT_OPTIONS[shortopt]
1599
975
            
1600
976
            if optname in opts:
1601
977
                # XXX: Do we ever want to support this, e.g. for -r?
1602
 
                raise BzrError('repeated option %r' % a)
 
978
                bailout('repeated option %r' % a)
1603
979
                
1604
980
            optargfn = OPTIONS[optname]
1605
981
            if optargfn:
1606
982
                if optarg == None:
1607
983
                    if not argv:
1608
 
                        raise BzrError('option %r needs an argument' % a)
 
984
                        bailout('option %r needs an argument' % a)
1609
985
                    else:
1610
986
                        optarg = argv.pop(0)
1611
987
                opts[optname] = optargfn(optarg)
1612
988
            else:
1613
989
                if optarg != None:
1614
 
                    raise BzrError('option %r takes no argument' % optname)
 
990
                    bailout('option %r takes no argument' % optname)
1615
991
                opts[optname] = True
1616
992
        else:
1617
993
            args.append(a)
1671
1047
 
1672
1048
    This is similar to main(), but without all the trappings for
1673
1049
    logging and error handling.  
1674
 
    
1675
 
    argv
1676
 
       The command-line arguments, without the program name from argv[0]
1677
 
    
1678
 
    Returns a command status or raises an exception.
1679
 
 
1680
 
    Special master options: these must come before the command because
1681
 
    they control how the command is interpreted.
1682
 
 
1683
 
    --no-plugins
1684
 
        Do not load plugin modules at all
1685
 
 
1686
 
    --builtin
1687
 
        Only use builtin commands.  (Plugins are still allowed to change
1688
 
        other behaviour.)
1689
 
 
1690
 
    --profile
1691
 
        Run under the Python profiler.
1692
1050
    """
1693
 
    
1694
1051
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
1695
 
 
1696
 
    opt_profile = opt_no_plugins = opt_builtin = False
1697
 
 
1698
 
    # --no-plugins is handled specially at a very early stage. We need
1699
 
    # to load plugins before doing other command parsing so that they
1700
 
    # can override commands, but this needs to happen first.
1701
 
 
1702
 
    for a in argv[:]:
1703
 
        if a == '--profile':
1704
 
            opt_profile = True
1705
 
        elif a == '--no-plugins':
1706
 
            opt_no_plugins = True
1707
 
        elif a == '--builtin':
1708
 
            opt_builtin = True
1709
 
        else:
1710
 
            break
1711
 
        argv.remove(a)
1712
 
 
1713
 
    if not opt_no_plugins:
1714
 
        from bzrlib.plugin import load_plugins
1715
 
        load_plugins()
1716
 
 
1717
 
    args, opts = parse_args(argv)
1718
 
 
1719
 
    if 'help' in opts:
1720
 
        from bzrlib.help import help
1721
 
        if args:
1722
 
            help(args[0])
1723
 
        else:
1724
 
            help()
1725
 
        return 0            
1726
 
        
1727
 
    if 'version' in opts:
1728
 
        show_version()
1729
 
        return 0
1730
1052
    
1731
 
    if not args:
1732
 
        print >>sys.stderr, "please try 'bzr help' for help"
 
1053
    try:
 
1054
        args, opts = parse_args(argv[1:])
 
1055
        if 'help' in opts:
 
1056
            import help
 
1057
            if args:
 
1058
                help.help(args[0])
 
1059
            else:
 
1060
                help.help()
 
1061
            return 0
 
1062
        elif 'version' in opts:
 
1063
            show_version()
 
1064
            return 0
 
1065
        cmd = str(args.pop(0))
 
1066
    except IndexError:
 
1067
        import help
 
1068
        help.help()
1733
1069
        return 1
1734
 
    
1735
 
    cmd = str(args.pop(0))
1736
 
 
1737
 
    canonical_cmd, cmd_class = \
1738
 
                   get_cmd_class(cmd, plugins_override=not opt_builtin)
 
1070
          
 
1071
 
 
1072
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
1073
 
 
1074
    # global option
 
1075
    if 'profile' in opts:
 
1076
        profile = True
 
1077
        del opts['profile']
 
1078
    else:
 
1079
        profile = False
1739
1080
 
1740
1081
    # check options are reasonable
1741
1082
    allowed = cmd_class.takes_options
1750
1091
    for k, v in opts.items():
1751
1092
        cmdopts[k.replace('-', '_')] = v
1752
1093
 
1753
 
    if opt_profile:
 
1094
    if profile:
1754
1095
        import hotshot, tempfile
1755
1096
        pffileno, pfname = tempfile.mkstemp()
1756
1097
        try:
1772
1113
            os.close(pffileno)
1773
1114
            os.remove(pfname)
1774
1115
    else:
1775
 
        return cmd_class(cmdopts, cmdargs).status 
 
1116
        cmdobj = cmd_class(cmdopts, cmdargs).status 
1776
1117
 
1777
1118
 
1778
1119
def _report_exception(summary, quiet=False):
1790
1131
 
1791
1132
 
1792
1133
def main(argv):
 
1134
    import errno
1793
1135
    
1794
 
    bzrlib.trace.open_tracefile(argv)
 
1136
    bzrlib.open_tracefile(argv)
1795
1137
 
1796
1138
    try:
1797
1139
        try:
1798
1140
            try:
1799
 
                return run_bzr(argv[1:])
 
1141
                return run_bzr(argv)
1800
1142
            finally:
1801
1143
                # do this here inside the exception wrappers to catch EPIPE
1802
1144
                sys.stdout.flush()
1803
1145
        except BzrError, e:
1804
1146
            quiet = isinstance(e, (BzrCommandError))
1805
 
            _report_exception('error: ' + str(e), quiet=quiet)
 
1147
            _report_exception('error: ' + e.args[0], quiet=quiet)
1806
1148
            if len(e.args) > 1:
1807
1149
                for h in e.args[1]:
1808
1150
                    # some explanation or hints
1818
1160
            _report_exception('interrupted', quiet=True)
1819
1161
            return 2
1820
1162
        except Exception, e:
1821
 
            import errno
1822
1163
            quiet = False
1823
1164
            if (isinstance(e, IOError) 
1824
1165
                and hasattr(e, 'errno')