~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Aaron Bentley
  • Date: 2005-07-26 14:06:11 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 982.
  • Revision ID: abentley@panoramicfeedback.com-20050726140611-403e366f3c79c1f1
Fixed python invocation

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
 
19
 
import sys, os, time, os.path
20
 
from sets import Set
 
19
import sys, os
21
20
 
22
21
import bzrlib
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, WorkingTree, Tree
27
 
from bzrlib.revision import Revision
28
 
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
29
 
     format_date
 
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__])
30
44
 
31
45
 
32
46
def _squish_command_name(cmd):
37
51
    assert cmd.startswith("cmd_")
38
52
    return cmd[4:].replace('_','-')
39
53
 
40
 
def get_all_cmds():
41
 
    """Return canonical name and class for all registered commands."""
 
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 = {}
42
128
    for k, v in globals().iteritems():
43
129
        if k.startswith("cmd_"):
44
 
            yield _unsquish_command_name(k), v
45
 
 
46
 
def get_cmd_class(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):
47
148
    """Return the canonical name and command class for a command.
48
149
    """
49
150
    cmd = str(cmd)                      # not unicode
50
151
 
51
152
    # first look up this command under the specified name
 
153
    cmds = _get_cmd_dict(plugins_override=plugins_override)
52
154
    try:
53
 
        return cmd, globals()[_squish_command_name(cmd)]
 
155
        return cmd, cmds[cmd]
54
156
    except KeyError:
55
157
        pass
56
158
 
57
159
    # look for any command which claims this as an alias
58
 
    for cmdname, cmdclass in get_all_cmds():
 
160
    for cmdname, cmdclass in cmds.iteritems():
59
161
        if cmd in cmdclass.aliases:
60
162
            return cmdname, cmdclass
61
163
 
66
168
    raise BzrCommandError("unknown command %r" % cmd)
67
169
 
68
170
 
69
 
class Command:
 
171
class Command(object):
70
172
    """Base class for commands.
71
173
 
72
174
    The docstring for an actual command should give a single-line
98
200
        assert isinstance(arguments, dict)
99
201
        cmdargs = options.copy()
100
202
        cmdargs.update(arguments)
101
 
        assert self.__doc__ != Command.__doc__, \
102
 
               ("No help message set for %r" % self)
 
203
        if self.__doc__ == Command.__doc__:
 
204
            from warnings import warn
 
205
            warn("No help message set for %r" % self)
103
206
        self.status = self.run(**cmdargs)
 
207
        if self.status is None:
 
208
            self.status = 0
104
209
 
105
210
    
106
211
    def run(self):
131
236
    """
132
237
 
133
238
    def find_command(cls, cmd):
 
239
        import os.path
134
240
        bzrpath = os.environ.get('BZRPATH', '')
135
241
 
136
 
        for dir in bzrpath.split(':'):
 
242
        for dir in bzrpath.split(os.pathsep):
137
243
            path = os.path.join(dir, cmd)
138
244
            if os.path.isfile(path):
139
245
                return ExternalCommand(path)
145
251
    def __init__(self, path):
146
252
        self.path = path
147
253
 
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
 
 
151
254
        pipe = os.popen('%s --bzr-usage' % path, 'r')
152
255
        self.takes_options = pipe.readline().split()
 
256
 
 
257
        for opt in self.takes_options:
 
258
            if not opt in OPTIONS:
 
259
                raise BzrError("Unknown option '%s' returned by external command %s"
 
260
                               % (opt, path))
 
261
 
 
262
        # TODO: Is there any way to check takes_args is valid here?
153
263
        self.takes_args = pipe.readline().split()
154
 
        pipe.close()
 
264
 
 
265
        if pipe.close() is not None:
 
266
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
155
267
 
156
268
        pipe = os.popen('%s --bzr-help' % path, 'r')
157
269
        self.__doc__ = pipe.read()
158
 
        pipe.close()
 
270
        if pipe.close() is not None:
 
271
            raise BzrError("Failed funning '%s --bzr-help'" % path)
159
272
 
160
273
    def __call__(self, options, arguments):
161
274
        Command.__init__(self, options, arguments)
168
281
        keys = kargs.keys()
169
282
        keys.sort()
170
283
        for name in keys:
 
284
            optname = name.replace('_','-')
171
285
            value = kargs[name]
172
 
            if OPTIONS.has_key(name):
 
286
            if OPTIONS.has_key(optname):
173
287
                # it's an option
174
 
                opts.append('--%s' % name)
 
288
                opts.append('--%s' % optname)
175
289
                if value is not None and value is not True:
176
290
                    opts.append(str(value))
177
291
            else:
189
303
class cmd_status(Command):
190
304
    """Display status summary.
191
305
 
192
 
    For each file there is a single line giving its file state and name.
193
 
    The name is that in the current revision unless it is deleted or
194
 
    missing, in which case the old name is shown.
 
306
    This reports on versioned and unknown files, reporting them
 
307
    grouped by state.  Possible states are:
 
308
 
 
309
    added
 
310
        Versioned in the working copy but not in the previous revision.
 
311
 
 
312
    removed
 
313
        Versioned in the previous revision but removed or deleted
 
314
        in the working copy.
 
315
 
 
316
    renamed
 
317
        Path of this file changed from the previous revision;
 
318
        the text may also have changed.  This includes files whose
 
319
        parent directory was renamed.
 
320
 
 
321
    modified
 
322
        Text has changed since the previous revision.
 
323
 
 
324
    unchanged
 
325
        Nothing about this file has changed since the previous revision.
 
326
        Only shown with --all.
 
327
 
 
328
    unknown
 
329
        Not versioned and not matching an ignore pattern.
 
330
 
 
331
    To see ignored files use 'bzr ignored'.  For details in the
 
332
    changes to file texts, use 'bzr diff'.
 
333
 
 
334
    If no arguments are specified, the status of the entire working
 
335
    directory is shown.  Otherwise, only the status of the specified
 
336
    files or directories is reported.  If a directory is given, status
 
337
    is reported for everything inside that directory.
 
338
 
 
339
    If a revision is specified, the changes since that revision are shown.
195
340
    """
196
341
    takes_args = ['file*']
197
 
    takes_options = ['all']
 
342
    takes_options = ['all', 'show-ids', 'revision']
198
343
    aliases = ['st', 'stat']
199
344
    
200
 
    def run(self, all=False, file_list=None):
201
 
        b = Branch('.', lock_mode='r')
202
 
        b.show_status(show_all=all, file_list=file_list)
 
345
    def run(self, all=False, show_ids=False, file_list=None):
 
346
        if file_list:
 
347
            b = find_branch(file_list[0])
 
348
            file_list = [b.relpath(x) for x in file_list]
 
349
            # special case: only one path was given and it's the root
 
350
            # of the branch
 
351
            if file_list == ['']:
 
352
                file_list = None
 
353
        else:
 
354
            b = find_branch('.')
 
355
            
 
356
        from bzrlib.status import show_status
 
357
        show_status(b, show_unchanged=all, show_ids=show_ids,
 
358
                    specific_files=file_list)
203
359
 
204
360
 
205
361
class cmd_cat_revision(Command):
209
365
    takes_args = ['revision_id']
210
366
    
211
367
    def run(self, revision_id):
212
 
        Branch('.').get_revision(revision_id).write_xml(sys.stdout)
 
368
        from bzrlib.xml import pack_xml
 
369
        pack_xml(find_branch('.').get_revision(revision_id), sys.stdout)
213
370
 
214
371
 
215
372
class cmd_revno(Command):
217
374
 
218
375
    This is equal to the number of revisions on this branch."""
219
376
    def run(self):
220
 
        print Branch('.').revno()
 
377
        print find_branch('.').revno()
 
378
 
 
379
class cmd_revision_info(Command):
 
380
    """Show revision number and revision id for a given revision identifier.
 
381
    """
 
382
    hidden = True
 
383
    takes_args = ['revision_info*']
 
384
    takes_options = ['revision']
 
385
    def run(self, revision=None, revision_info_list=None):
 
386
        from bzrlib.branch import find_branch
 
387
 
 
388
        revs = []
 
389
        if revision is not None:
 
390
            revs.extend(revision)
 
391
        if revision_info_list is not None:
 
392
            revs.extend(revision_info_list)
 
393
        if len(revs) == 0:
 
394
            raise BzrCommandError('You must supply a revision identifier')
 
395
 
 
396
        b = find_branch('.')
 
397
 
 
398
        for rev in revs:
 
399
            print '%4d %s' % b.get_revision_info(rev)
221
400
 
222
401
    
223
402
class cmd_add(Command):
233
412
    whether already versioned or not, are searched for files or
234
413
    subdirectories that are neither versioned or ignored, and these
235
414
    are added.  This search proceeds recursively into versioned
236
 
    directories.
 
415
    directories.  If no names are given '.' is assumed.
237
416
 
238
 
    Therefore simply saying 'bzr add .' will version all files that
 
417
    Therefore simply saying 'bzr add' will version all files that
239
418
    are currently unknown.
240
419
 
241
420
    TODO: Perhaps adding a file whose directly is not versioned should
242
421
    recursively add that parent, rather than giving an error?
243
422
    """
244
 
    takes_args = ['file+']
245
 
    takes_options = ['verbose']
 
423
    takes_args = ['file*']
 
424
    takes_options = ['verbose', 'no-recurse']
246
425
    
247
 
    def run(self, file_list, verbose=False):
248
 
        bzrlib.add.smart_add(file_list, verbose)
 
426
    def run(self, file_list, verbose=False, no_recurse=False):
 
427
        from bzrlib.add import smart_add
 
428
        smart_add(file_list, verbose, not no_recurse)
 
429
 
 
430
 
 
431
 
 
432
class cmd_mkdir(Command):
 
433
    """Create a new versioned directory.
 
434
 
 
435
    This is equivalent to creating the directory and then adding it.
 
436
    """
 
437
    takes_args = ['dir+']
 
438
 
 
439
    def run(self, dir_list):
 
440
        b = None
 
441
        
 
442
        for d in dir_list:
 
443
            os.mkdir(d)
 
444
            if not b:
 
445
                b = find_branch(d)
 
446
            b.add([d], verbose=True)
249
447
 
250
448
 
251
449
class cmd_relpath(Command):
252
450
    """Show path of a file relative to root"""
253
451
    takes_args = ['filename']
 
452
    hidden = True
254
453
    
255
454
    def run(self, filename):
256
 
        print Branch(filename).relpath(filename)
 
455
        print find_branch(filename).relpath(filename)
257
456
 
258
457
 
259
458
 
260
459
class cmd_inventory(Command):
261
460
    """Show inventory of the current working copy or a revision."""
262
 
    takes_options = ['revision']
 
461
    takes_options = ['revision', 'show-ids']
263
462
    
264
 
    def run(self, revision=None):
265
 
        b = Branch('.')
 
463
    def run(self, revision=None, show_ids=False):
 
464
        b = find_branch('.')
266
465
        if revision == None:
267
466
            inv = b.read_working_inventory()
268
467
        else:
269
 
            inv = b.get_revision_inventory(b.lookup_revision(revision))
 
468
            if len(revision) > 1:
 
469
                raise BzrCommandError('bzr inventory --revision takes'
 
470
                    ' exactly one revision identifier')
 
471
            inv = b.get_revision_inventory(b.lookup_revision(revision[0]))
270
472
 
271
 
        for path, entry in inv.iter_entries():
272
 
            print '%-50s %s' % (entry.file_id, path)
 
473
        for path, entry in inv.entries():
 
474
            if show_ids:
 
475
                print '%-50s %s' % (path, entry.file_id)
 
476
            else:
 
477
                print path
273
478
 
274
479
 
275
480
class cmd_move(Command):
282
487
    """
283
488
    takes_args = ['source$', 'dest']
284
489
    def run(self, source_list, dest):
285
 
        b = Branch('.')
 
490
        b = find_branch('.')
286
491
 
287
492
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
288
493
 
304
509
    takes_args = ['from_name', 'to_name']
305
510
    
306
511
    def run(self, from_name, to_name):
307
 
        b = Branch('.')
 
512
        b = find_branch('.')
308
513
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
309
514
 
310
515
 
311
516
 
 
517
 
 
518
 
 
519
class cmd_pull(Command):
 
520
    """Pull any changes from another branch into the current one.
 
521
 
 
522
    If the location is omitted, the last-used location will be used.
 
523
    Both the revision history and the working directory will be
 
524
    updated.
 
525
 
 
526
    This command only works on branches that have not diverged.  Branches are
 
527
    considered diverged if both branches have had commits without first
 
528
    pulling from the other.
 
529
 
 
530
    If branches have diverged, you can use 'bzr merge' to pull the text changes
 
531
    from one into the other.
 
532
    """
 
533
    takes_args = ['location?']
 
534
 
 
535
    def run(self, location=None):
 
536
        from bzrlib.merge import merge
 
537
        import tempfile
 
538
        from shutil import rmtree
 
539
        import errno
 
540
        
 
541
        br_to = find_branch('.')
 
542
        stored_loc = None
 
543
        try:
 
544
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
 
545
        except IOError, e:
 
546
            if e.errno != errno.ENOENT:
 
547
                raise
 
548
        if location is None:
 
549
            if stored_loc is None:
 
550
                raise BzrCommandError("No pull location known or specified.")
 
551
            else:
 
552
                print "Using last location: %s" % stored_loc
 
553
                location = stored_loc
 
554
        cache_root = tempfile.mkdtemp()
 
555
        from bzrlib.branch import DivergedBranches
 
556
        br_from = find_branch(location)
 
557
        location = pull_loc(br_from)
 
558
        old_revno = br_to.revno()
 
559
        try:
 
560
            from branch import find_cached_branch, DivergedBranches
 
561
            br_from = find_cached_branch(location, cache_root)
 
562
            location = pull_loc(br_from)
 
563
            old_revno = br_to.revno()
 
564
            try:
 
565
                br_to.update_revisions(br_from)
 
566
            except DivergedBranches:
 
567
                raise BzrCommandError("These branches have diverged."
 
568
                    "  Try merge.")
 
569
                
 
570
            merge(('.', -1), ('.', old_revno), check_clean=False)
 
571
            if location != stored_loc:
 
572
                br_to.controlfile("x-pull", "wb").write(location + "\n")
 
573
        finally:
 
574
            rmtree(cache_root)
 
575
 
 
576
 
 
577
 
 
578
class cmd_branch(Command):
 
579
    """Create a new copy of a branch.
 
580
 
 
581
    If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
 
582
    be used.  In other words, "branch ../foo/bar" will attempt to create ./bar.
 
583
 
 
584
    To retrieve the branch as of a particular revision, supply the --revision
 
585
    parameter, as in "branch foo/bar -r 5".
 
586
    """
 
587
    takes_args = ['from_location', 'to_location?']
 
588
    takes_options = ['revision']
 
589
 
 
590
    def run(self, from_location, to_location=None, revision=None):
 
591
        import errno
 
592
        from bzrlib.merge import merge
 
593
        from bzrlib.branch import DivergedBranches, NoSuchRevision, \
 
594
             find_cached_branch, Branch
 
595
        from shutil import rmtree
 
596
        from meta_store import CachedStore
 
597
        import tempfile
 
598
        cache_root = tempfile.mkdtemp()
 
599
 
 
600
        if revision is None:
 
601
            revision = [None]
 
602
        elif len(revision) > 1:
 
603
            raise BzrCommandError('bzr branch --revision takes exactly 1 revision value')
 
604
 
 
605
        try:
 
606
            try:
 
607
                br_from = find_cached_branch(from_location, cache_root)
 
608
            except OSError, e:
 
609
                if e.errno == errno.ENOENT:
 
610
                    raise BzrCommandError('Source location "%s" does not'
 
611
                                          ' exist.' % to_location)
 
612
                else:
 
613
                    raise
 
614
 
 
615
            if to_location is None:
 
616
                to_location = os.path.basename(from_location.rstrip("/\\"))
 
617
 
 
618
            try:
 
619
                os.mkdir(to_location)
 
620
            except OSError, e:
 
621
                if e.errno == errno.EEXIST:
 
622
                    raise BzrCommandError('Target directory "%s" already'
 
623
                                          ' exists.' % to_location)
 
624
                if e.errno == errno.ENOENT:
 
625
                    raise BzrCommandError('Parent of "%s" does not exist.' %
 
626
                                          to_location)
 
627
                else:
 
628
                    raise
 
629
            br_to = Branch(to_location, init=True)
 
630
 
 
631
            br_to.set_root_id(br_from.get_root_id())
 
632
 
 
633
            if revision:
 
634
                if revision[0] is None:
 
635
                    revno = br_from.revno()
 
636
                else:
 
637
                    revno, rev_id = br_from.get_revision_info(revision[0])
 
638
                try:
 
639
                    br_to.update_revisions(br_from, stop_revision=revno)
 
640
                except NoSuchRevision:
 
641
                    rmtree(to_location)
 
642
                    msg = "The branch %s has no revision %d." % (from_location,
 
643
                                                                 revno)
 
644
                    raise BzrCommandError(msg)
 
645
            
 
646
            merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
647
                  check_clean=False, ignore_zero=True)
 
648
            from_location = pull_loc(br_from)
 
649
            br_to.controlfile("x-pull", "wb").write(from_location + "\n")
 
650
        finally:
 
651
            rmtree(cache_root)
 
652
 
 
653
 
 
654
def pull_loc(branch):
 
655
    # TODO: Should perhaps just make attribute be 'base' in
 
656
    # RemoteBranch and Branch?
 
657
    if hasattr(branch, "baseurl"):
 
658
        return branch.baseurl
 
659
    else:
 
660
        return branch.base
 
661
 
 
662
 
 
663
 
312
664
class cmd_renames(Command):
313
665
    """Show list of renamed files.
314
666
 
319
671
    takes_args = ['dir?']
320
672
 
321
673
    def run(self, dir='.'):
322
 
        b = Branch(dir)
 
674
        b = find_branch(dir)
323
675
        old_inv = b.basis_tree().inventory
324
676
        new_inv = b.read_working_inventory()
325
677
 
330
682
 
331
683
 
332
684
class cmd_info(Command):
333
 
    """Show statistical information for this branch"""
334
 
    def run(self):
 
685
    """Show statistical information about a branch."""
 
686
    takes_args = ['branch?']
 
687
    
 
688
    def run(self, branch=None):
335
689
        import info
336
 
        info.show_info(Branch('.'))        
 
690
 
 
691
        b = find_branch(branch)
 
692
        info.show_info(b)
337
693
 
338
694
 
339
695
class cmd_remove(Command):
346
702
    takes_options = ['verbose']
347
703
    
348
704
    def run(self, file_list, verbose=False):
349
 
        b = Branch(file_list[0])
 
705
        b = find_branch(file_list[0])
350
706
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
351
707
 
352
708
 
360
716
    hidden = True
361
717
    takes_args = ['filename']
362
718
    def run(self, filename):
363
 
        b = Branch(filename)
 
719
        b = find_branch(filename)
364
720
        i = b.inventory.path2id(b.relpath(filename))
365
721
        if i == None:
366
 
            bailout("%r is not a versioned file" % filename)
 
722
            raise BzrError("%r is not a versioned file" % filename)
367
723
        else:
368
724
            print i
369
725
 
376
732
    hidden = True
377
733
    takes_args = ['filename']
378
734
    def run(self, filename):
379
 
        b = Branch(filename)
 
735
        b = find_branch(filename)
380
736
        inv = b.inventory
381
737
        fid = inv.path2id(b.relpath(filename))
382
738
        if fid == None:
383
 
            bailout("%r is not a versioned file" % filename)
 
739
            raise BzrError("%r is not a versioned file" % filename)
384
740
        for fip in inv.get_idpath(fid):
385
741
            print fip
386
742
 
387
743
 
388
744
class cmd_revision_history(Command):
389
745
    """Display list of revision ids on this branch."""
 
746
    hidden = True
390
747
    def run(self):
391
 
        for patchid in Branch('.').revision_history():
 
748
        for patchid in find_branch('.').revision_history():
392
749
            print patchid
393
750
 
394
751
 
395
752
class cmd_directories(Command):
396
753
    """Display list of versioned directories in this branch."""
397
754
    def run(self):
398
 
        for name, ie in Branch('.').read_working_inventory().directories():
 
755
        for name, ie in find_branch('.').read_working_inventory().directories():
399
756
            if name == '':
400
757
                print '.'
401
758
            else:
416
773
        bzr commit -m 'imported project'
417
774
    """
418
775
    def run(self):
 
776
        from bzrlib.branch import Branch
419
777
        Branch('.', init=True)
420
778
 
421
779
 
444
802
    """
445
803
    
446
804
    takes_args = ['file*']
447
 
    takes_options = ['revision']
448
 
    aliases = ['di']
 
805
    takes_options = ['revision', 'diff-options']
 
806
    aliases = ['di', 'dif']
449
807
 
450
 
    def run(self, revision=None, file_list=None):
 
808
    def run(self, revision=None, file_list=None, diff_options=None):
451
809
        from bzrlib.diff import show_diff
 
810
 
 
811
        if file_list:
 
812
            b = find_branch(file_list[0])
 
813
            file_list = [b.relpath(f) for f in file_list]
 
814
            if file_list == ['']:
 
815
                # just pointing to top-of-tree
 
816
                file_list = None
 
817
        else:
 
818
            b = find_branch('.')
 
819
 
 
820
        # TODO: Make show_diff support taking 2 arguments
 
821
        base_rev = None
 
822
        if revision is not None:
 
823
            if len(revision) != 1:
 
824
                raise BzrCommandError('bzr diff --revision takes exactly one revision identifier')
 
825
            base_rev = revision[0]
452
826
    
453
 
        show_diff(Branch('.'), revision, file_list)
 
827
        show_diff(b, base_rev, specific_files=file_list,
 
828
                  external_diff_options=diff_options)
454
829
 
455
830
 
456
831
        
462
837
    TODO: Show files deleted since a previous revision, or between two revisions.
463
838
    """
464
839
    def run(self, show_ids=False):
465
 
        b = Branch('.')
 
840
        b = find_branch('.')
466
841
        old = b.basis_tree()
467
842
        new = b.working_tree()
468
843
 
483
858
    """List files modified in working tree."""
484
859
    hidden = True
485
860
    def run(self):
486
 
        import statcache
487
 
        b = Branch('.')
488
 
        inv = b.read_working_inventory()
489
 
        sc = statcache.update_cache(b, inv)
490
 
        basis = b.basis_tree()
491
 
        basis_inv = basis.inventory
492
 
        
493
 
        # We used to do this through iter_entries(), but that's slow
494
 
        # when most of the files are unmodified, as is usually the
495
 
        # case.  So instead we iterate by inventory entry, and only
496
 
        # calculate paths as necessary.
497
 
 
498
 
        for file_id in basis_inv:
499
 
            cacheentry = sc.get(file_id)
500
 
            if not cacheentry:                 # deleted
501
 
                continue
502
 
            ie = basis_inv[file_id]
503
 
            if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
504
 
                path = inv.id2path(file_id)
505
 
                print path
 
861
        from bzrlib.diff import compare_trees
 
862
 
 
863
        b = find_branch('.')
 
864
        td = compare_trees(b.basis_tree(), b.working_tree())
 
865
 
 
866
        for path, id, kind in td.modified:
 
867
            print path
506
868
 
507
869
 
508
870
 
510
872
    """List files added in working tree."""
511
873
    hidden = True
512
874
    def run(self):
513
 
        b = Branch('.')
 
875
        b = find_branch('.')
514
876
        wt = b.working_tree()
515
877
        basis_inv = b.basis_tree().inventory
516
878
        inv = wt.inventory
532
894
    takes_args = ['filename?']
533
895
    def run(self, filename=None):
534
896
        """Print the branch root."""
535
 
        from branch import find_branch
536
897
        b = find_branch(filename)
537
898
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
538
899
 
540
901
class cmd_log(Command):
541
902
    """Show log of this branch.
542
903
 
543
 
    TODO: Option to limit range.
544
 
 
545
 
    TODO: Perhaps show most-recent first with an option for last.
 
904
    To request a range of logs, you can use the command -r begin:end
 
905
    -r revision requests a specific revision, -r :end or -r begin: are
 
906
    also valid.
 
907
 
 
908
    --message allows you to give a regular expression, which will be evaluated
 
909
    so that only matching entries will be displayed.
 
910
 
 
911
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
 
912
  
546
913
    """
 
914
 
547
915
    takes_args = ['filename?']
548
 
    takes_options = ['timezone', 'verbose', 'show-ids']
549
 
    def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
550
 
        from branch import find_branch
551
 
        b = find_branch((filename or '.'), lock_mode='r')
 
916
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision','long', 'message']
 
917
    
 
918
    def run(self, filename=None, timezone='original',
 
919
            verbose=False,
 
920
            show_ids=False,
 
921
            forward=False,
 
922
            revision=None,
 
923
            message=None,
 
924
            long=False):
 
925
        from bzrlib.branch import find_branch
 
926
        from bzrlib.log import log_formatter, show_log
 
927
        import codecs
 
928
 
 
929
        direction = (forward and 'forward') or 'reverse'
 
930
        
552
931
        if filename:
553
 
            filename = b.relpath(filename)
554
 
        bzrlib.show_log(b, filename,
555
 
                        show_timezone=timezone,
556
 
                        verbose=verbose,
557
 
                        show_ids=show_ids)
 
932
            b = find_branch(filename)
 
933
            fp = b.relpath(filename)
 
934
            if fp:
 
935
                file_id = b.read_working_inventory().path2id(fp)
 
936
            else:
 
937
                file_id = None  # points to branch root
 
938
        else:
 
939
            b = find_branch('.')
 
940
            file_id = None
 
941
 
 
942
        if revision is None:
 
943
            rev1 = None
 
944
            rev2 = None
 
945
        elif len(revision) == 1:
 
946
            rev1 = rev2 = b.get_revision_info(revision[0])[0]
 
947
        elif len(revision) == 2:
 
948
            rev1 = b.get_revision_info(revision[0])[0]
 
949
            rev2 = b.get_revision_info(revision[1])[0]
 
950
        else:
 
951
            raise BzrCommandError('bzr log --revision takes one or two values.')
 
952
 
 
953
        if rev1 == 0:
 
954
            rev1 = None
 
955
        if rev2 == 0:
 
956
            rev2 = None
 
957
 
 
958
        mutter('encoding log as %r' % bzrlib.user_encoding)
 
959
 
 
960
        # use 'replace' so that we don't abort if trying to write out
 
961
        # in e.g. the default C locale.
 
962
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
 
963
 
 
964
        if long:
 
965
            log_format = 'long'
 
966
        else:
 
967
            log_format = 'short'
 
968
        lf = log_formatter(log_format,
 
969
                           show_ids=show_ids,
 
970
                           to_file=outf,
 
971
                           show_timezone=timezone)
 
972
 
 
973
        show_log(b,
 
974
                 lf,
 
975
                 file_id,
 
976
                 verbose=verbose,
 
977
                 direction=direction,
 
978
                 start_revision=rev1,
 
979
                 end_revision=rev2,
 
980
                 search=message)
558
981
 
559
982
 
560
983
 
561
984
class cmd_touching_revisions(Command):
562
 
    """Return revision-ids which affected a particular file."""
 
985
    """Return revision-ids which affected a particular file.
 
986
 
 
987
    A more user-friendly interface is "bzr log FILE"."""
563
988
    hidden = True
564
989
    takes_args = ["filename"]
565
990
    def run(self, filename):
566
 
        b = Branch(filename, lock_mode='r')
 
991
        b = find_branch(filename)
567
992
        inv = b.read_working_inventory()
568
993
        file_id = inv.path2id(b.relpath(filename))
569
994
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
577
1002
    """
578
1003
    hidden = True
579
1004
    def run(self, revision=None, verbose=False):
580
 
        b = Branch('.')
 
1005
        b = find_branch('.')
581
1006
        if revision == None:
582
1007
            tree = b.working_tree()
583
1008
        else:
599
1024
 
600
1025
 
601
1026
class cmd_unknowns(Command):
602
 
    """List unknown files"""
 
1027
    """List unknown files."""
603
1028
    def run(self):
604
 
        for f in Branch('.').unknowns():
 
1029
        from bzrlib.osutils import quotefn
 
1030
        for f in find_branch('.').unknowns():
605
1031
            print quotefn(f)
606
1032
 
607
1033
 
608
1034
 
609
1035
class cmd_ignore(Command):
610
 
    """Ignore a command or pattern
 
1036
    """Ignore a command or pattern.
611
1037
 
612
1038
    To remove patterns from the ignore list, edit the .bzrignore file.
613
1039
 
627
1053
    
628
1054
    def run(self, name_pattern):
629
1055
        from bzrlib.atomicfile import AtomicFile
630
 
        import codecs
 
1056
        import os.path
631
1057
 
632
 
        b = Branch('.')
 
1058
        b = find_branch('.')
633
1059
        ifn = b.abspath('.bzrignore')
634
1060
 
635
 
        # FIXME: probably doesn't handle non-ascii patterns
636
 
 
637
1061
        if os.path.exists(ifn):
638
 
            f = b.controlfile(ifn, 'rt')
639
 
            igns = f.read()
640
 
            f.close()
 
1062
            f = open(ifn, 'rt')
 
1063
            try:
 
1064
                igns = f.read().decode('utf-8')
 
1065
            finally:
 
1066
                f.close()
641
1067
        else:
642
1068
            igns = ''
643
1069
 
 
1070
        # TODO: If the file already uses crlf-style termination, maybe
 
1071
        # we should use that for the newly added lines?
 
1072
 
644
1073
        if igns and igns[-1] != '\n':
645
1074
            igns += '\n'
646
1075
        igns += name_pattern + '\n'
647
1076
 
648
 
        f = AtomicFile(ifn, 'wt')
649
 
        f.write(igns)
650
 
        f.commit()
 
1077
        try:
 
1078
            f = AtomicFile(ifn, 'wt')
 
1079
            f.write(igns.encode('utf-8'))
 
1080
            f.commit()
 
1081
        finally:
 
1082
            f.close()
651
1083
 
652
1084
        inv = b.working_tree().inventory
653
1085
        if inv.path2id('.bzrignore'):
663
1095
 
664
1096
    See also: bzr ignore"""
665
1097
    def run(self):
666
 
        tree = Branch('.').working_tree()
 
1098
        tree = find_branch('.').working_tree()
667
1099
        for path, file_class, kind, file_id in tree.list_files():
668
1100
            if file_class != 'I':
669
1101
                continue
687
1119
        except ValueError:
688
1120
            raise BzrCommandError("not a valid revision-number: %r" % revno)
689
1121
 
690
 
        print Branch('.').lookup_revision(revno)
 
1122
        print find_branch('.').lookup_revision(revno)
691
1123
 
692
1124
 
693
1125
class cmd_export(Command):
694
1126
    """Export past revision to destination directory.
695
1127
 
696
 
    If no revision is specified this exports the last committed revision."""
 
1128
    If no revision is specified this exports the last committed revision.
 
1129
 
 
1130
    Format may be an "exporter" name, such as tar, tgz, tbz2.  If none is
 
1131
    given, try to find the format with the extension. If no extension
 
1132
    is found exports to a directory (equivalent to --format=dir).
 
1133
 
 
1134
    Root may be the top directory for tar, tgz and tbz2 formats. If none
 
1135
    is given, the top directory will be the root name of the file."""
 
1136
    # TODO: list known exporters
697
1137
    takes_args = ['dest']
698
 
    takes_options = ['revision']
699
 
    def run(self, dest, revision=None):
700
 
        b = Branch('.')
701
 
        if revision == None:
702
 
            rh = b.revision_history()[-1]
 
1138
    takes_options = ['revision', 'format', 'root']
 
1139
    def run(self, dest, revision=None, format=None, root=None):
 
1140
        import os.path
 
1141
        b = find_branch('.')
 
1142
        if revision is None:
 
1143
            rev_id = b.last_patch()
703
1144
        else:
704
 
            rh = b.lookup_revision(int(revision))
705
 
        t = b.revision_tree(rh)
706
 
        t.export(dest)
 
1145
            if len(revision) != 1:
 
1146
                raise BzrError('bzr export --revision takes exactly 1 argument')
 
1147
            revno, rev_id = b.get_revision_info(revision[0])
 
1148
        t = b.revision_tree(rev_id)
 
1149
        root, ext = os.path.splitext(dest)
 
1150
        if not format:
 
1151
            if ext in (".tar",):
 
1152
                format = "tar"
 
1153
            elif ext in (".gz", ".tgz"):
 
1154
                format = "tgz"
 
1155
            elif ext in (".bz2", ".tbz2"):
 
1156
                format = "tbz2"
 
1157
            else:
 
1158
                format = "dir"
 
1159
        t.export(dest, format, root)
707
1160
 
708
1161
 
709
1162
class cmd_cat(Command):
715
1168
    def run(self, filename, revision=None):
716
1169
        if revision == None:
717
1170
            raise BzrCommandError("bzr cat requires a revision number")
718
 
        b = Branch('.')
719
 
        b.print_file(b.relpath(filename), int(revision))
 
1171
        elif len(revision) != 1:
 
1172
            raise BzrCommandError("bzr cat --revision takes exactly one number")
 
1173
        b = find_branch('.')
 
1174
        b.print_file(b.relpath(filename), revision[0])
720
1175
 
721
1176
 
722
1177
class cmd_local_time_offset(Command):
730
1185
class cmd_commit(Command):
731
1186
    """Commit changes into a new revision.
732
1187
 
733
 
    TODO: Commit only selected files.
 
1188
    If selected files are specified, only changes to those files are
 
1189
    committed.  If a directory is specified then its contents are also
 
1190
    committed.
 
1191
 
 
1192
    A selected-file commit may fail in some cases where the committed
 
1193
    tree would be invalid, such as trying to commit a file in a
 
1194
    newly-added directory that is not itself committed.
734
1195
 
735
1196
    TODO: Run hooks on tree to-be-committed, and after commit.
736
1197
 
737
1198
    TODO: Strict commit that fails if there are unknown or deleted files.
738
1199
    """
739
 
    takes_options = ['message', 'file', 'verbose']
 
1200
    takes_args = ['selected*']
 
1201
    takes_options = ['message', 'file', 'verbose', 'unchanged']
740
1202
    aliases = ['ci', 'checkin']
741
1203
 
742
 
    def run(self, message=None, file=None, verbose=False):
 
1204
    def run(self, message=None, file=None, verbose=True, selected_list=None,
 
1205
            unchanged=False):
 
1206
        from bzrlib.errors import PointlessCommit
 
1207
        from bzrlib.osutils import get_text_message
 
1208
 
743
1209
        ## Warning: shadows builtin file()
744
1210
        if not message and not file:
745
 
            raise BzrCommandError("please specify a commit message",
746
 
                                  ["use either --message or --file"])
 
1211
            import cStringIO
 
1212
            stdout = sys.stdout
 
1213
            catcher = cStringIO.StringIO()
 
1214
            sys.stdout = catcher
 
1215
            cmd_status({"file_list":selected_list}, {})
 
1216
            info = catcher.getvalue()
 
1217
            sys.stdout = stdout
 
1218
            message = get_text_message(info)
 
1219
            
 
1220
            if message is None:
 
1221
                raise BzrCommandError("please specify a commit message",
 
1222
                                      ["use either --message or --file"])
747
1223
        elif message and file:
748
1224
            raise BzrCommandError("please specify either --message or --file")
749
1225
        
751
1227
            import codecs
752
1228
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
753
1229
 
754
 
        Branch('.').commit(message, verbose=verbose)
 
1230
        b = find_branch('.')
 
1231
 
 
1232
        try:
 
1233
            b.commit(message, verbose=verbose,
 
1234
                     specific_files=selected_list,
 
1235
                     allow_pointless=unchanged)
 
1236
        except PointlessCommit:
 
1237
            # FIXME: This should really happen before the file is read in;
 
1238
            # perhaps prepare the commit; get the message; then actually commit
 
1239
            raise BzrCommandError("no changes to commit",
 
1240
                                  ["use --unchanged to commit anyhow"])
755
1241
 
756
1242
 
757
1243
class cmd_check(Command):
759
1245
 
760
1246
    This command checks various invariants about the branch storage to
761
1247
    detect data corruption or bzr bugs.
762
 
    """
763
 
    takes_args = ['dir?']
764
 
    def run(self, dir='.'):
765
 
        import bzrlib.check
766
 
        bzrlib.check.check(Branch(dir, find_root=False))
 
1248
 
 
1249
    If given the --update flag, it will update some optional fields
 
1250
    to help ensure data consistency.
 
1251
    """
 
1252
    takes_args = ['dir?']
 
1253
 
 
1254
    def run(self, dir='.'):
 
1255
        from bzrlib.check import check
 
1256
        check(find_branch(dir))
 
1257
 
 
1258
 
 
1259
 
 
1260
class cmd_scan_cache(Command):
 
1261
    hidden = True
 
1262
    def run(self):
 
1263
        from bzrlib.hashcache import HashCache
 
1264
        import os
 
1265
 
 
1266
        c = HashCache('.')
 
1267
        c.read()
 
1268
        c.scan()
 
1269
            
 
1270
        print '%6d stats' % c.stat_count
 
1271
        print '%6d in hashcache' % len(c._cache)
 
1272
        print '%6d files removed from cache' % c.removed_count
 
1273
        print '%6d hashes updated' % c.update_count
 
1274
        print '%6d files changed too recently to cache' % c.danger_count
 
1275
 
 
1276
        if c.needs_write:
 
1277
            c.write()
 
1278
            
 
1279
 
 
1280
 
 
1281
class cmd_upgrade(Command):
 
1282
    """Upgrade branch storage to current format.
 
1283
 
 
1284
    This should normally be used only after the check command tells
 
1285
    you to run it.
 
1286
    """
 
1287
    takes_args = ['dir?']
 
1288
 
 
1289
    def run(self, dir='.'):
 
1290
        from bzrlib.upgrade import upgrade
 
1291
        upgrade(find_branch(dir))
767
1292
 
768
1293
 
769
1294
 
781
1306
class cmd_selftest(Command):
782
1307
    """Run internal test suite"""
783
1308
    hidden = True
784
 
    def run(self):
785
 
        failures, tests = 0, 0
786
 
 
787
 
        import doctest, bzrlib.store, bzrlib.tests
788
 
        bzrlib.trace.verbose = False
789
 
 
790
 
        for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
791
 
            bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
792
 
            mf, mt = doctest.testmod(m)
793
 
            failures += mf
794
 
            tests += mt
795
 
            print '%-40s %3d tests' % (m.__name__, mt),
796
 
            if mf:
797
 
                print '%3d FAILED!' % mf
798
 
            else:
799
 
                print
800
 
 
801
 
        print '%-40s %3d tests' % ('total', tests),
802
 
        if failures:
803
 
            print '%3d FAILED!' % failures
804
 
        else:
805
 
            print
806
 
 
 
1309
    takes_options = ['verbose']
 
1310
    def run(self, verbose=False):
 
1311
        from bzrlib.selftest import selftest
 
1312
        return int(not selftest(verbose=verbose))
807
1313
 
808
1314
 
809
1315
class cmd_version(Command):
810
 
    """Show version of bzr"""
 
1316
    """Show version of bzr."""
811
1317
    def run(self):
812
1318
        show_version()
813
1319
 
814
1320
def show_version():
815
1321
    print "bzr (bazaar-ng) %s" % bzrlib.__version__
 
1322
    # is bzrlib itself in a branch?
 
1323
    bzrrev = bzrlib.get_bzr_revision()
 
1324
    if bzrrev:
 
1325
        print "  (bzr checkout, revision %d {%s})" % bzrrev
816
1326
    print bzrlib.__copyright__
817
1327
    print "http://bazaar-ng.org/"
818
1328
    print
827
1337
    def run(self):
828
1338
        print "it sure does!"
829
1339
 
 
1340
def parse_spec(spec):
 
1341
    """
 
1342
    >>> parse_spec(None)
 
1343
    [None, None]
 
1344
    >>> parse_spec("./")
 
1345
    ['./', None]
 
1346
    >>> parse_spec("../@")
 
1347
    ['..', -1]
 
1348
    >>> parse_spec("../f/@35")
 
1349
    ['../f', 35]
 
1350
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
1351
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
 
1352
    """
 
1353
    if spec is None:
 
1354
        return [None, None]
 
1355
    if '/@' in spec:
 
1356
        parsed = spec.split('/@')
 
1357
        assert len(parsed) == 2
 
1358
        if parsed[1] == "":
 
1359
            parsed[1] = -1
 
1360
        else:
 
1361
            try:
 
1362
                parsed[1] = int(parsed[1])
 
1363
            except ValueError:
 
1364
                pass # We can allow stuff like ./@revid:blahblahblah
 
1365
            else:
 
1366
                assert parsed[1] >=0
 
1367
    else:
 
1368
        parsed = [spec, None]
 
1369
    return parsed
 
1370
 
 
1371
 
 
1372
 
 
1373
class cmd_merge(Command):
 
1374
    """Perform a three-way merge of trees.
 
1375
    
 
1376
    The SPEC parameters are working tree or revision specifiers.  Working trees
 
1377
    are specified using standard paths or urls.  No component of a directory
 
1378
    path may begin with '@'.
 
1379
    
 
1380
    Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
 
1381
 
 
1382
    Revisions are specified using a dirname/@revno pair, where dirname is the
 
1383
    branch directory and revno is the revision within that branch.  If no revno
 
1384
    is specified, the latest revision is used.
 
1385
 
 
1386
    Revision examples: './@127', 'foo/@', '../@1'
 
1387
 
 
1388
    The OTHER_SPEC parameter is required.  If the BASE_SPEC parameter is
 
1389
    not supplied, the common ancestor of OTHER_SPEC the current branch is used
 
1390
    as the BASE.
 
1391
 
 
1392
    merge refuses to run if there are any uncommitted changes, unless
 
1393
    --force is given.
 
1394
    """
 
1395
    takes_args = ['other_spec', 'base_spec?']
 
1396
    takes_options = ['force']
 
1397
 
 
1398
    def run(self, other_spec, base_spec=None, force=False):
 
1399
        from bzrlib.merge import merge
 
1400
        merge(parse_spec(other_spec), parse_spec(base_spec),
 
1401
              check_clean=(not force))
 
1402
 
 
1403
 
 
1404
 
 
1405
class cmd_revert(Command):
 
1406
    """Restore selected files from a previous revision.
 
1407
    """
 
1408
    takes_args = ['file+']
 
1409
    def run(self, file_list):
 
1410
        from bzrlib.branch import find_branch
 
1411
        
 
1412
        if not file_list:
 
1413
            file_list = ['.']
 
1414
            
 
1415
        b = find_branch(file_list[0])
 
1416
 
 
1417
        b.revert([b.relpath(f) for f in file_list])
 
1418
 
 
1419
 
 
1420
class cmd_merge_revert(Command):
 
1421
    """Reverse all changes since the last commit.
 
1422
 
 
1423
    Only versioned files are affected.
 
1424
 
 
1425
    TODO: Store backups of any files that will be reverted, so
 
1426
          that the revert can be undone.          
 
1427
    """
 
1428
    takes_options = ['revision']
 
1429
 
 
1430
    def run(self, revision=None):
 
1431
        from bzrlib.merge import merge
 
1432
        if revision is None:
 
1433
            revision = [-1]
 
1434
        elif len(revision) != 1:
 
1435
            raise BzrCommandError('bzr merge-revert --revision takes exactly 1 argument')
 
1436
        merge(('.', revision[0]), parse_spec('.'),
 
1437
              check_clean=False,
 
1438
              ignore_zero=True)
 
1439
 
830
1440
 
831
1441
class cmd_assert_fail(Command):
832
1442
    """Test reporting of assertion failures"""
847
1457
        help.help(topic)
848
1458
 
849
1459
 
850
 
class cmd_update_stat_cache(Command):
851
 
    """Update stat-cache mapping inodes to SHA-1 hashes.
852
 
 
853
 
    For testing only."""
 
1460
 
 
1461
 
 
1462
class cmd_plugins(Command):
 
1463
    """List plugins"""
854
1464
    hidden = True
855
1465
    def run(self):
856
 
        import statcache
857
 
        b = Branch('.')
858
 
        statcache.update_cache(b)
859
 
 
860
 
 
861
 
######################################################################
862
 
# main routine
 
1466
        import bzrlib.plugin
 
1467
        from inspect import getdoc
 
1468
        from pprint import pprint
 
1469
        for plugin in bzrlib.plugin.all_plugins:
 
1470
            print plugin.__path__[0]
 
1471
            d = getdoc(plugin)
 
1472
            if d:
 
1473
                print '\t', d.split('\n')[0]
 
1474
 
 
1475
        #pprint(bzrlib.plugin.all_plugins)
 
1476
 
863
1477
 
864
1478
 
865
1479
# list of all available options; the rhs can be either None for an
867
1481
# the type.
868
1482
OPTIONS = {
869
1483
    'all':                    None,
 
1484
    'diff-options':           str,
870
1485
    'help':                   None,
871
1486
    'file':                   unicode,
 
1487
    'force':                  None,
 
1488
    'format':                 unicode,
 
1489
    'forward':                None,
872
1490
    'message':                unicode,
 
1491
    'no-recurse':             None,
873
1492
    'profile':                None,
874
 
    'revision':               int,
 
1493
    'revision':               _parse_revision_str,
875
1494
    'show-ids':               None,
876
1495
    'timezone':               str,
877
1496
    'verbose':                None,
878
1497
    'version':                None,
879
1498
    'email':                  None,
 
1499
    'unchanged':              None,
 
1500
    'update':                 None,
 
1501
    'long':                   None,
 
1502
    'root':                   str,
880
1503
    }
881
1504
 
882
1505
SHORT_OPTIONS = {
 
1506
    'F':                      'file', 
 
1507
    'h':                      'help',
883
1508
    'm':                      'message',
884
 
    'F':                      'file', 
885
1509
    'r':                      'revision',
886
1510
    'v':                      'verbose',
 
1511
    'l':                      'long',
887
1512
}
888
1513
 
889
1514
 
903
1528
    (['status'], {'all': True})
904
1529
    >>> parse_args('commit --message=biter'.split())
905
1530
    (['commit'], {'message': u'biter'})
 
1531
    >>> parse_args('log -r 500'.split())
 
1532
    (['log'], {'revision': [500]})
 
1533
    >>> parse_args('log -r500..600'.split())
 
1534
    (['log'], {'revision': [500, 600]})
 
1535
    >>> parse_args('log -vr500..600'.split())
 
1536
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
1537
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
1538
    (['log'], {'revision': ['v500', 600]})
906
1539
    """
907
1540
    args = []
908
1541
    opts = {}
922
1555
                else:
923
1556
                    optname = a[2:]
924
1557
                if optname not in OPTIONS:
925
 
                    bailout('unknown long option %r' % a)
 
1558
                    raise BzrError('unknown long option %r' % a)
926
1559
            else:
927
1560
                shortopt = a[1:]
928
 
                if shortopt not in SHORT_OPTIONS:
929
 
                    bailout('unknown short option %r' % a)
930
 
                optname = SHORT_OPTIONS[shortopt]
 
1561
                if shortopt in SHORT_OPTIONS:
 
1562
                    # Multi-character options must have a space to delimit
 
1563
                    # their value
 
1564
                    optname = SHORT_OPTIONS[shortopt]
 
1565
                else:
 
1566
                    # Single character short options, can be chained,
 
1567
                    # and have their value appended to their name
 
1568
                    shortopt = a[1:2]
 
1569
                    if shortopt not in SHORT_OPTIONS:
 
1570
                        # We didn't find the multi-character name, and we
 
1571
                        # didn't find the single char name
 
1572
                        raise BzrError('unknown short option %r' % a)
 
1573
                    optname = SHORT_OPTIONS[shortopt]
 
1574
 
 
1575
                    if a[2:]:
 
1576
                        # There are extra things on this option
 
1577
                        # see if it is the value, or if it is another
 
1578
                        # short option
 
1579
                        optargfn = OPTIONS[optname]
 
1580
                        if optargfn is None:
 
1581
                            # This option does not take an argument, so the
 
1582
                            # next entry is another short option, pack it back
 
1583
                            # into the list
 
1584
                            argv.insert(0, '-' + a[2:])
 
1585
                        else:
 
1586
                            # This option takes an argument, so pack it
 
1587
                            # into the array
 
1588
                            optarg = a[2:]
931
1589
            
932
1590
            if optname in opts:
933
1591
                # XXX: Do we ever want to support this, e.g. for -r?
934
 
                bailout('repeated option %r' % a)
 
1592
                raise BzrError('repeated option %r' % a)
935
1593
                
936
1594
            optargfn = OPTIONS[optname]
937
1595
            if optargfn:
938
1596
                if optarg == None:
939
1597
                    if not argv:
940
 
                        bailout('option %r needs an argument' % a)
 
1598
                        raise BzrError('option %r needs an argument' % a)
941
1599
                    else:
942
1600
                        optarg = argv.pop(0)
943
1601
                opts[optname] = optargfn(optarg)
944
1602
            else:
945
1603
                if optarg != None:
946
 
                    bailout('option %r takes no argument' % optname)
 
1604
                    raise BzrError('option %r takes no argument' % optname)
947
1605
                opts[optname] = True
948
1606
        else:
949
1607
            args.append(a)
997
1655
    return argdict
998
1656
 
999
1657
 
 
1658
def _parse_master_args(argv):
 
1659
    """Parse the arguments that always go with the original command.
 
1660
    These are things like bzr --no-plugins, etc.
 
1661
 
 
1662
    There are now 2 types of option flags. Ones that come *before* the command,
 
1663
    and ones that come *after* the command.
 
1664
    Ones coming *before* the command are applied against all possible commands.
 
1665
    And are generally applied before plugins are loaded.
 
1666
 
 
1667
    The current list are:
 
1668
        --builtin   Allow plugins to load, but don't let them override builtin commands,
 
1669
                    they will still be allowed if they do not override a builtin.
 
1670
        --no-plugins    Don't load any plugins. This lets you get back to official source
 
1671
                        behavior.
 
1672
        --profile   Enable the hotspot profile before running the command.
 
1673
                    For backwards compatibility, this is also a non-master option.
 
1674
        --version   Spit out the version of bzr that is running and exit.
 
1675
                    This is also a non-master option.
 
1676
        --help      Run help and exit, also a non-master option (I think that should stay, though)
 
1677
 
 
1678
    >>> argv, opts = _parse_master_args(['--test'])
 
1679
    Traceback (most recent call last):
 
1680
    ...
 
1681
    BzrCommandError: Invalid master option: 'test'
 
1682
    >>> argv, opts = _parse_master_args(['--version', 'command'])
 
1683
    >>> print argv
 
1684
    ['command']
 
1685
    >>> print opts['version']
 
1686
    True
 
1687
    >>> argv, opts = _parse_master_args(['--profile', 'command', '--more-options'])
 
1688
    >>> print argv
 
1689
    ['command', '--more-options']
 
1690
    >>> print opts['profile']
 
1691
    True
 
1692
    >>> argv, opts = _parse_master_args(['--no-plugins', 'command'])
 
1693
    >>> print argv
 
1694
    ['command']
 
1695
    >>> print opts['no-plugins']
 
1696
    True
 
1697
    >>> print opts['profile']
 
1698
    False
 
1699
    >>> argv, opts = _parse_master_args(['command', '--profile'])
 
1700
    >>> print argv
 
1701
    ['command', '--profile']
 
1702
    >>> print opts['profile']
 
1703
    False
 
1704
    """
 
1705
    master_opts = {'builtin':False,
 
1706
        'no-plugins':False,
 
1707
        'version':False,
 
1708
        'profile':False,
 
1709
        'help':False
 
1710
    }
 
1711
 
 
1712
    for arg in argv[:]:
 
1713
        if arg[:2] != '--': # at the first non-option, we return the rest
 
1714
            break
 
1715
        arg = arg[2:] # Remove '--'
 
1716
        if arg not in master_opts:
 
1717
            # We could say that this is not an error, that we should
 
1718
            # just let it be handled by the main section instead
 
1719
            raise BzrCommandError('Invalid master option: %r' % arg)
 
1720
        argv.pop(0) # We are consuming this entry
 
1721
        master_opts[arg] = True
 
1722
    return argv, master_opts
 
1723
 
 
1724
 
1000
1725
 
1001
1726
def run_bzr(argv):
1002
1727
    """Execute a command.
1003
1728
 
1004
1729
    This is similar to main(), but without all the trappings for
1005
1730
    logging and error handling.  
 
1731
    
 
1732
    argv
 
1733
       The command-line arguments, without the program name.
 
1734
    
 
1735
    Returns a command status or raises an exception.
1006
1736
    """
1007
1737
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
1738
 
 
1739
    # some options like --builtin and --no-plugins have special effects
 
1740
    argv, master_opts = _parse_master_args(argv)
 
1741
    if not master_opts['no-plugins']:
 
1742
        from bzrlib.plugin import load_plugins
 
1743
        load_plugins()
 
1744
 
 
1745
    args, opts = parse_args(argv)
 
1746
 
 
1747
    if master_opts.get('help') or 'help' in opts:
 
1748
        from bzrlib.help import help
 
1749
        if argv:
 
1750
            help(argv[0])
 
1751
        else:
 
1752
            help()
 
1753
        return 0            
 
1754
        
 
1755
    if 'version' in opts:
 
1756
        show_version()
 
1757
        return 0
 
1758
    
 
1759
    if args and args[0] == 'builtin':
 
1760
        include_plugins=False
 
1761
        args = args[1:]
1008
1762
    
1009
1763
    try:
1010
 
        args, opts = parse_args(argv[1:])
1011
 
        if 'help' in opts:
1012
 
            import help
1013
 
            if args:
1014
 
                help.help(args[0])
1015
 
            else:
1016
 
                help.help()
1017
 
            return 0
1018
 
        elif 'version' in opts:
1019
 
            show_version()
1020
 
            return 0
1021
1764
        cmd = str(args.pop(0))
1022
1765
    except IndexError:
1023
 
        log_error('usage: bzr COMMAND')
1024
 
        log_error('  try "bzr help"')
 
1766
        print >>sys.stderr, "please try 'bzr help' for help"
1025
1767
        return 1
1026
1768
 
1027
 
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
1769
    plugins_override = not (master_opts['builtin'])
 
1770
    canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
1028
1771
 
1029
 
    # global option
 
1772
    profile = master_opts['profile']
 
1773
    # For backwards compatibility, I would rather stick with --profile being a
 
1774
    # master/global option
1030
1775
    if 'profile' in opts:
1031
1776
        profile = True
1032
1777
        del opts['profile']
1033
 
    else:
1034
 
        profile = False
1035
1778
 
1036
1779
    # check options are reasonable
1037
1780
    allowed = cmd_class.takes_options
1068
1811
            os.close(pffileno)
1069
1812
            os.remove(pfname)
1070
1813
    else:
1071
 
        cmdobj = cmd_class(cmdopts, cmdargs).status 
 
1814
        return cmd_class(cmdopts, cmdargs).status 
1072
1815
 
1073
1816
 
1074
1817
def _report_exception(summary, quiet=False):
1086
1829
 
1087
1830
 
1088
1831
def main(argv):
1089
 
    import errno
1090
1832
    
1091
 
    bzrlib.open_tracefile(argv)
 
1833
    bzrlib.trace.open_tracefile(argv)
1092
1834
 
1093
1835
    try:
1094
1836
        try:
1095
1837
            try:
1096
 
                return run_bzr(argv)
 
1838
                return run_bzr(argv[1:])
1097
1839
            finally:
1098
1840
                # do this here inside the exception wrappers to catch EPIPE
1099
1841
                sys.stdout.flush()
1115
1857
            _report_exception('interrupted', quiet=True)
1116
1858
            return 2
1117
1859
        except Exception, e:
 
1860
            import errno
1118
1861
            quiet = False
1119
1862
            if (isinstance(e, IOError) 
1120
1863
                and hasattr(e, 'errno')