~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/builtins.py

  • Committer: Robert Collins
  • Date: 2005-10-23 11:46:19 UTC
  • Revision ID: robertc@robertcollins.net-20051023114619-cec8ca25207b1344
More quoting at the transport layer bugfixes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
 
 
17
# DO NOT change this to cStringIO - it results in control files 
 
18
# written as UCS4
 
19
# FIXIT! (Only deal with byte streams OR unicode at any one layer.)
 
20
# RBC 20051018
 
21
from StringIO import StringIO
18
22
import sys
19
23
import os
20
24
 
21
25
import bzrlib
 
26
from bzrlib import BZRDIR
 
27
from bzrlib.commands import Command, display_command
 
28
from bzrlib.branch import Branch
 
29
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
 
30
from bzrlib.errors import DivergedBranches
 
31
from bzrlib.option import Option
 
32
from bzrlib.revisionspec import RevisionSpec
22
33
import bzrlib.trace
23
34
from bzrlib.trace import mutter, note, log_error, warning
24
 
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
25
 
from bzrlib.branch import find_branch
26
 
from bzrlib import BZRDIR
27
 
from bzrlib.commands import Command
 
35
from bzrlib.workingtree import WorkingTree
28
36
 
29
37
 
30
38
class cmd_status(Command):
63
71
    files or directories is reported.  If a directory is given, status
64
72
    is reported for everything inside that directory.
65
73
 
66
 
    If a revision is specified, the changes since that revision are shown.
 
74
    If a revision argument is given, the status is calculated against
 
75
    that revision, or between two revisions if two are provided.
67
76
    """
 
77
    
 
78
    # XXX: FIXME: bzr status should accept a -r option to show changes
 
79
    # relative to a revision, or between revisions
 
80
 
 
81
    # TODO: --no-recurse, --recurse options
 
82
    
68
83
    takes_args = ['file*']
69
 
    takes_options = ['all', 'show-ids', 'revision']
 
84
    takes_options = ['all', 'show-ids']
70
85
    aliases = ['st', 'stat']
71
86
    
72
 
    def run(self, all=False, show_ids=False, file_list=None):
 
87
    @display_command
 
88
    def run(self, all=False, show_ids=False, file_list=None, revision=None):
73
89
        if file_list:
74
 
            b = find_branch(file_list[0])
75
 
            file_list = [b.relpath(x) for x in file_list]
76
 
            # special case: only one path was given and it's the root
77
 
            # of the branch
78
 
            if file_list == ['']:
 
90
            b, relpath = Branch.open_containing(file_list[0])
 
91
            if relpath == '' and len(file_list) == 1:
79
92
                file_list = None
 
93
            else:
 
94
                # generate relative paths.
 
95
                # note that if this is a remote branch, we would want
 
96
                # relpath against the transport. RBC 20051018
 
97
                tree = WorkingTree(b.base, b)
 
98
                file_list = [tree.relpath(x) for x in file_list]
80
99
        else:
81
 
            b = find_branch('.')
 
100
            b = Branch.open_containing('.')[0]
82
101
            
83
102
        from bzrlib.status import show_status
84
103
        show_status(b, show_unchanged=all, show_ids=show_ids,
85
 
                    specific_files=file_list)
 
104
                    specific_files=file_list, revision=revision)
86
105
 
87
106
 
88
107
class cmd_cat_revision(Command):
89
 
    """Write out metadata for a revision."""
 
108
    """Write out metadata for a revision.
 
109
    
 
110
    The revision to print can either be specified by a specific
 
111
    revision identifier, or you can use --revision.
 
112
    """
90
113
 
91
114
    hidden = True
92
 
    takes_args = ['revision_id']
 
115
    takes_args = ['revision_id?']
 
116
    takes_options = ['revision']
93
117
    
94
 
    def run(self, revision_id):
95
 
        b = find_branch('.')
96
 
        sys.stdout.write(b.get_revision_xml_file(revision_id).read())
 
118
    @display_command
 
119
    def run(self, revision_id=None, revision=None):
97
120
 
 
121
        if revision_id is not None and revision is not None:
 
122
            raise BzrCommandError('You can only supply one of revision_id or --revision')
 
123
        if revision_id is None and revision is None:
 
124
            raise BzrCommandError('You must supply either --revision or a revision_id')
 
125
        b = Branch.open_containing('.')[0]
 
126
        if revision_id is not None:
 
127
            sys.stdout.write(b.get_revision_xml_file(revision_id).read())
 
128
        elif revision is not None:
 
129
            for rev in revision:
 
130
                if rev is None:
 
131
                    raise BzrCommandError('You cannot specify a NULL revision.')
 
132
                revno, rev_id = rev.in_history(b)
 
133
                sys.stdout.write(b.get_revision_xml_file(rev_id).read())
 
134
    
98
135
 
99
136
class cmd_revno(Command):
100
137
    """Show current revision number.
101
138
 
102
139
    This is equal to the number of revisions on this branch."""
 
140
    @display_command
103
141
    def run(self):
104
 
        print find_branch('.').revno()
 
142
        print Branch.open_containing('.')[0].revno()
105
143
 
106
144
 
107
145
class cmd_revision_info(Command):
110
148
    hidden = True
111
149
    takes_args = ['revision_info*']
112
150
    takes_options = ['revision']
113
 
    def run(self, revision=None, revision_info_list=None):
114
 
        from bzrlib.branch import find_branch
 
151
    @display_command
 
152
    def run(self, revision=None, revision_info_list=[]):
115
153
 
116
154
        revs = []
117
155
        if revision is not None:
118
156
            revs.extend(revision)
119
157
        if revision_info_list is not None:
120
 
            revs.extend(revision_info_list)
 
158
            for rev in revision_info_list:
 
159
                revs.append(RevisionSpec(rev))
121
160
        if len(revs) == 0:
122
161
            raise BzrCommandError('You must supply a revision identifier')
123
162
 
124
 
        b = find_branch('.')
 
163
        b = Branch.open_containing('.')[0]
125
164
 
126
165
        for rev in revs:
127
 
            print '%4d %s' % b.get_revision_info(rev)
 
166
            revinfo = rev.in_history(b)
 
167
            if revinfo.revno is None:
 
168
                print '     %s' % revinfo.rev_id
 
169
            else:
 
170
                print '%4d %s' % (revinfo.revno, revinfo.rev_id)
128
171
 
129
172
    
130
173
class cmd_add(Command):
145
188
    Therefore simply saying 'bzr add' will version all files that
146
189
    are currently unknown.
147
190
 
148
 
    TODO: Perhaps adding a file whose directly is not versioned should
149
 
    recursively add that parent, rather than giving an error?
 
191
    Adding a file whose parent directory is not versioned will
 
192
    implicitly add the parent, and so on up to the root. This means
 
193
    you should never need to explictly add a directory, they'll just
 
194
    get added when you add a file in the directory.
150
195
    """
151
196
    takes_args = ['file*']
152
 
    takes_options = ['verbose', 'no-recurse']
 
197
    takes_options = ['no-recurse', 'quiet']
153
198
    
154
 
    def run(self, file_list, verbose=False, no_recurse=False):
155
 
        # verbose currently has no effect
156
 
        from bzrlib.add import smart_add, add_reporter_print
157
 
        smart_add(file_list, not no_recurse, add_reporter_print)
158
 
 
 
199
    def run(self, file_list, no_recurse=False, quiet=False):
 
200
        from bzrlib.add import smart_add, add_reporter_print, add_reporter_null
 
201
        if quiet:
 
202
            reporter = add_reporter_null
 
203
        else:
 
204
            reporter = add_reporter_print
 
205
        smart_add(file_list, not no_recurse, reporter)
159
206
 
160
207
 
161
208
class cmd_mkdir(Command):
171
218
        for d in dir_list:
172
219
            os.mkdir(d)
173
220
            if not b:
174
 
                b = find_branch(d)
 
221
                b = Branch.open_containing(d)[0]
175
222
            b.add([d])
176
223
            print 'added', d
177
224
 
181
228
    takes_args = ['filename']
182
229
    hidden = True
183
230
    
 
231
    @display_command
184
232
    def run(self, filename):
185
 
        print find_branch(filename).relpath(filename)
186
 
 
 
233
        branch, relpath = Branch.open_containing(filename)
 
234
        print relpath
187
235
 
188
236
 
189
237
class cmd_inventory(Command):
190
238
    """Show inventory of the current working copy or a revision."""
191
239
    takes_options = ['revision', 'show-ids']
192
240
    
 
241
    @display_command
193
242
    def run(self, revision=None, show_ids=False):
194
 
        b = find_branch('.')
195
 
        if revision == None:
 
243
        b = Branch.open_containing('.')[0]
 
244
        if revision is None:
196
245
            inv = b.read_working_inventory()
197
246
        else:
198
247
            if len(revision) > 1:
199
248
                raise BzrCommandError('bzr inventory --revision takes'
200
249
                    ' exactly one revision identifier')
201
 
            inv = b.get_revision_inventory(b.lookup_revision(revision[0]))
 
250
            inv = b.get_revision_inventory(revision[0].in_history(b).rev_id)
202
251
 
203
252
        for path, entry in inv.entries():
204
253
            if show_ids:
217
266
    """
218
267
    takes_args = ['source$', 'dest']
219
268
    def run(self, source_list, dest):
220
 
        b = find_branch('.')
 
269
        b = Branch.open_containing('.')[0]
221
270
 
222
271
        # TODO: glob expansion on windows?
223
 
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
272
        tree = WorkingTree(b.base, b)
 
273
        b.move([tree.relpath(s) for s in source_list], tree.relpath(dest))
224
274
 
225
275
 
226
276
class cmd_rename(Command):
234
284
 
235
285
    See also the 'move' command, which moves files into a different
236
286
    directory without changing their name.
237
 
 
238
 
    TODO: Some way to rename multiple files without invoking bzr for each
239
 
    one?"""
 
287
    """
 
288
    # TODO: Some way to rename multiple files without invoking 
 
289
    # bzr for each one?"""
240
290
    takes_args = ['from_name', 'to_name']
241
291
    
242
292
    def run(self, from_name, to_name):
243
 
        b = find_branch('.')
244
 
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
245
 
 
 
293
        b = Branch.open_containing('.')[0]
 
294
        tree = WorkingTree(b.base, b)
 
295
        b.rename_one(tree.relpath(from_name), tree.relpath(to_name))
246
296
 
247
297
 
248
298
class cmd_mv(Command):
262
312
    def run(self, names_list):
263
313
        if len(names_list) < 2:
264
314
            raise BzrCommandError("missing file argument")
265
 
        b = find_branch(names_list[0])
266
 
 
267
 
        rel_names = [b.relpath(x) for x in names_list]
 
315
        b = Branch.open_containing(names_list[0])[0]
 
316
        tree = WorkingTree(b.base, b)
 
317
        rel_names = [tree.relpath(x) for x in names_list]
268
318
        
269
319
        if os.path.isdir(names_list[-1]):
270
320
            # move into existing directory
274
324
            if len(names_list) != 2:
275
325
                raise BzrCommandError('to mv multiple files the destination '
276
326
                                      'must be a versioned directory')
277
 
            for pair in b.move(rel_names[0], rel_names[1]):
278
 
                print "%s => %s" % pair
 
327
            b.rename_one(rel_names[0], rel_names[1])
 
328
            print "%s => %s" % (rel_names[0], rel_names[1])
279
329
            
280
330
    
281
331
 
294
344
    If branches have diverged, you can use 'bzr merge' to pull the text changes
295
345
    from one into the other.
296
346
    """
 
347
    takes_options = ['remember', 'clobber']
297
348
    takes_args = ['location?']
298
349
 
299
 
    def run(self, location=None):
 
350
    def run(self, location=None, remember=False, clobber=False):
300
351
        from bzrlib.merge import merge
301
 
        import tempfile
302
352
        from shutil import rmtree
303
353
        import errno
304
 
        from bzrlib.branch import pull_loc
305
354
        
306
 
        br_to = find_branch('.')
307
 
        stored_loc = None
308
 
        try:
309
 
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
310
 
        except IOError, e:
311
 
            if e.errno != errno.ENOENT:
312
 
                raise
 
355
        br_to = Branch.open_containing('.')[0]
 
356
        stored_loc = br_to.get_parent()
313
357
        if location is None:
314
358
            if stored_loc is None:
315
359
                raise BzrCommandError("No pull location known or specified.")
316
360
            else:
317
 
                print "Using last location: %s" % stored_loc
 
361
                print "Using saved location: %s" % stored_loc
318
362
                location = stored_loc
319
 
        cache_root = tempfile.mkdtemp()
320
 
        from bzrlib.branch import DivergedBranches
321
 
        br_from = find_branch(location)
322
 
        location = pull_loc(br_from)
323
 
        old_revno = br_to.revno()
 
363
        br_from = Branch.open(location)
324
364
        try:
325
 
            from branch import find_cached_branch, DivergedBranches
326
 
            br_from = find_cached_branch(location, cache_root)
327
 
            location = pull_loc(br_from)
328
 
            old_revno = br_to.revno()
329
 
            try:
330
 
                br_to.update_revisions(br_from)
331
 
            except DivergedBranches:
332
 
                raise BzrCommandError("These branches have diverged."
333
 
                    "  Try merge.")
334
 
                
335
 
            merge(('.', -1), ('.', old_revno), check_clean=False)
336
 
            if location != stored_loc:
337
 
                br_to.controlfile("x-pull", "wb").write(location + "\n")
338
 
        finally:
339
 
            rmtree(cache_root)
340
 
 
 
365
            br_to.working_tree().pull(br_from, remember, clobber)
 
366
        except DivergedBranches:
 
367
            raise BzrCommandError("These branches have diverged."
 
368
                                  "  Try merge.")
341
369
 
342
370
 
343
371
class cmd_branch(Command):
348
376
 
349
377
    To retrieve the branch as of a particular revision, supply the --revision
350
378
    parameter, as in "branch foo/bar -r 5".
 
379
 
 
380
    --basis is to speed up branching from remote branches.  When specified, it
 
381
    copies all the file-contents, inventory and revision data from the basis
 
382
    branch before copying anything from the remote branch.
351
383
    """
352
384
    takes_args = ['from_location', 'to_location?']
353
 
    takes_options = ['revision']
 
385
    takes_options = ['revision', 'basis']
354
386
    aliases = ['get', 'clone']
355
387
 
356
 
    def run(self, from_location, to_location=None, revision=None):
357
 
        from bzrlib.branch import copy_branch, find_cached_branch
358
 
        import tempfile
 
388
    def run(self, from_location, to_location=None, revision=None, basis=None):
 
389
        from bzrlib.clone import copy_branch
359
390
        import errno
360
391
        from shutil import rmtree
361
 
        cache_root = tempfile.mkdtemp()
362
 
        try:
363
 
            if revision is None:
364
 
                revision = [None]
365
 
            elif len(revision) > 1:
366
 
                raise BzrCommandError(
367
 
                    'bzr branch --revision takes exactly 1 revision value')
368
 
            try:
369
 
                br_from = find_cached_branch(from_location, cache_root)
370
 
            except OSError, e:
371
 
                if e.errno == errno.ENOENT:
372
 
                    raise BzrCommandError('Source location "%s" does not'
373
 
                                          ' exist.' % to_location)
374
 
                else:
375
 
                    raise
 
392
        if revision is None:
 
393
            revision = [None]
 
394
        elif len(revision) > 1:
 
395
            raise BzrCommandError(
 
396
                'bzr branch --revision takes exactly 1 revision value')
 
397
        try:
 
398
            br_from = Branch.open(from_location)
 
399
        except OSError, e:
 
400
            if e.errno == errno.ENOENT:
 
401
                raise BzrCommandError('Source location "%s" does not'
 
402
                                      ' exist.' % to_location)
 
403
            else:
 
404
                raise
 
405
        br_from.lock_read()
 
406
        try:
 
407
            if basis is not None:
 
408
                basis_branch = Branch.open_containing(basis)[0]
 
409
            else:
 
410
                basis_branch = None
 
411
            if len(revision) == 1 and revision[0] is not None:
 
412
                revision_id = revision[0].in_history(br_from)[1]
 
413
            else:
 
414
                revision_id = None
376
415
            if to_location is None:
377
416
                to_location = os.path.basename(from_location.rstrip("/\\"))
 
417
                name = None
 
418
            else:
 
419
                name = os.path.basename(to_location) + '\n'
378
420
            try:
379
421
                os.mkdir(to_location)
380
422
            except OSError, e:
387
429
                else:
388
430
                    raise
389
431
            try:
390
 
                copy_branch(br_from, to_location, revision[0])
 
432
                copy_branch(br_from, to_location, revision_id, basis_branch)
391
433
            except bzrlib.errors.NoSuchRevision:
392
434
                rmtree(to_location)
393
 
                msg = "The branch %s has no revision %d." % (from_location, revision[0])
394
 
                raise BzrCommandError(msg)
 
435
                msg = "The branch %s has no revision %s." % (from_location, revision[0])
 
436
                raise BzrCommandError(msg)
 
437
            except bzrlib.errors.UnlistableBranch:
 
438
                rmtree(to_location)
 
439
                msg = "The branch %s cannot be used as a --basis"
 
440
                raise BzrCommandError(msg)
 
441
            if name:
 
442
                branch = Branch.open(to_location)
 
443
                name = StringIO(name)
 
444
                branch.put_controlfile('branch-name', name)
395
445
        finally:
396
 
            rmtree(cache_root)
 
446
            br_from.unlock()
397
447
 
398
448
 
399
449
class cmd_renames(Command):
400
450
    """Show list of renamed files.
401
 
 
402
 
    TODO: Option to show renames between two historical versions.
403
 
 
404
 
    TODO: Only show renames under dir, rather than in the whole branch.
405
451
    """
 
452
    # TODO: Option to show renames between two historical versions.
 
453
 
 
454
    # TODO: Only show renames under dir, rather than in the whole branch.
406
455
    takes_args = ['dir?']
407
456
 
 
457
    @display_command
408
458
    def run(self, dir='.'):
409
 
        b = find_branch(dir)
 
459
        b = Branch.open_containing(dir)[0]
410
460
        old_inv = b.basis_tree().inventory
411
461
        new_inv = b.read_working_inventory()
412
462
 
420
470
    """Show statistical information about a branch."""
421
471
    takes_args = ['branch?']
422
472
    
 
473
    @display_command
423
474
    def run(self, branch=None):
424
475
        import info
425
 
 
426
 
        b = find_branch(branch)
 
476
        b = Branch.open_containing(branch)[0]
427
477
        info.show_info(b)
428
478
 
429
479
 
435
485
    """
436
486
    takes_args = ['file+']
437
487
    takes_options = ['verbose']
 
488
    aliases = ['rm']
438
489
    
439
490
    def run(self, file_list, verbose=False):
440
 
        b = find_branch(file_list[0])
441
 
        b.remove([b.relpath(f) for f in file_list], verbose=verbose)
 
491
        b = Branch.open_containing(file_list[0])[0]
 
492
        tree = WorkingTree(b.base, b)
 
493
        tree.remove([tree.relpath(f) for f in file_list], verbose=verbose)
442
494
 
443
495
 
444
496
class cmd_file_id(Command):
450
502
    """
451
503
    hidden = True
452
504
    takes_args = ['filename']
 
505
    @display_command
453
506
    def run(self, filename):
454
 
        b = find_branch(filename)
455
 
        i = b.inventory.path2id(b.relpath(filename))
 
507
        b, relpath = Branch.open_containing(filename)
 
508
        i = b.inventory.path2id(relpath)
456
509
        if i == None:
457
510
            raise BzrError("%r is not a versioned file" % filename)
458
511
        else:
466
519
    starting at the branch root."""
467
520
    hidden = True
468
521
    takes_args = ['filename']
 
522
    @display_command
469
523
    def run(self, filename):
470
 
        b = find_branch(filename)
 
524
        b, relpath = Branch.open_containing(filename)
471
525
        inv = b.inventory
472
 
        fid = inv.path2id(b.relpath(filename))
 
526
        fid = inv.path2id(relpath)
473
527
        if fid == None:
474
528
            raise BzrError("%r is not a versioned file" % filename)
475
529
        for fip in inv.get_idpath(fid):
479
533
class cmd_revision_history(Command):
480
534
    """Display list of revision ids on this branch."""
481
535
    hidden = True
 
536
    @display_command
482
537
    def run(self):
483
 
        for patchid in find_branch('.').revision_history():
 
538
        for patchid in Branch.open_containing('.')[0].revision_history():
484
539
            print patchid
485
540
 
486
541
 
487
542
class cmd_ancestry(Command):
488
543
    """List all revisions merged into this branch."""
489
544
    hidden = True
 
545
    @display_command
490
546
    def run(self):
491
 
        b = find_branch('.')
 
547
        b = Branch.open_containing('.')[0]
492
548
        for revision_id in b.get_ancestry(b.last_revision()):
493
549
            print revision_id
494
550
 
495
551
 
496
552
class cmd_directories(Command):
497
553
    """Display list of versioned directories in this branch."""
 
554
    @display_command
498
555
    def run(self):
499
 
        for name, ie in find_branch('.').read_working_inventory().directories():
 
556
        for name, ie in Branch.open_containing('.')[0].read_working_inventory().directories():
500
557
            if name == '':
501
558
                print '.'
502
559
            else:
517
574
        bzr commit -m 'imported project'
518
575
    """
519
576
    def run(self):
520
 
        from bzrlib.branch import Branch
521
 
        Branch('.', init=True)
 
577
        Branch.initialize('.')
522
578
 
523
579
 
524
580
class cmd_diff(Command):
527
583
    If files are listed, only the changes in those files are listed.
528
584
    Otherwise, all changes for the tree are listed.
529
585
 
530
 
    TODO: Allow diff across branches.
531
 
 
532
 
    TODO: Option to use external diff command; could be GNU diff, wdiff,
533
 
          or a graphical diff.
534
 
 
535
 
    TODO: Python difflib is not exactly the same as unidiff; should
536
 
          either fix it up or prefer to use an external diff.
537
 
 
538
 
    TODO: If a directory is given, diff everything under that.
539
 
 
540
 
    TODO: Selected-file diff is inefficient and doesn't show you
541
 
          deleted files.
542
 
 
543
 
    TODO: This probably handles non-Unix newlines poorly.
544
 
 
545
586
    examples:
546
587
        bzr diff
547
588
        bzr diff -r1
548
 
        bzr diff -r1:2
 
589
        bzr diff -r1..2
549
590
    """
 
591
    # TODO: Allow diff across branches.
 
592
    # TODO: Option to use external diff command; could be GNU diff, wdiff,
 
593
    #       or a graphical diff.
 
594
 
 
595
    # TODO: Python difflib is not exactly the same as unidiff; should
 
596
    #       either fix it up or prefer to use an external diff.
 
597
 
 
598
    # TODO: If a directory is given, diff everything under that.
 
599
 
 
600
    # TODO: Selected-file diff is inefficient and doesn't show you
 
601
    #       deleted files.
 
602
 
 
603
    # TODO: This probably handles non-Unix newlines poorly.
550
604
    
551
605
    takes_args = ['file*']
552
606
    takes_options = ['revision', 'diff-options']
553
607
    aliases = ['di', 'dif']
554
608
 
 
609
    @display_command
555
610
    def run(self, revision=None, file_list=None, diff_options=None):
556
611
        from bzrlib.diff import show_diff
557
612
 
558
613
        if file_list:
559
 
            b = find_branch(file_list[0])
560
 
            file_list = [b.relpath(f) for f in file_list]
 
614
            b = Branch.open_containing(file_list[0])[0]
 
615
            tree = WorkingTree(b.base, b)
 
616
            file_list = [tree.relpath(f) for f in file_list]
561
617
            if file_list == ['']:
562
618
                # just pointing to top-of-tree
563
619
                file_list = None
564
620
        else:
565
 
            b = find_branch('.')
 
621
            b = Branch.open_containing('.')[0]
566
622
 
567
623
        if revision is not None:
568
624
            if len(revision) == 1:
583
639
 
584
640
class cmd_deleted(Command):
585
641
    """List files deleted in the working tree.
586
 
 
587
 
    TODO: Show files deleted since a previous revision, or between two revisions.
588
642
    """
 
643
    # TODO: Show files deleted since a previous revision, or
 
644
    # between two revisions.
 
645
    # TODO: Much more efficient way to do this: read in new
 
646
    # directories with readdir, rather than stating each one.  Same
 
647
    # level of effort but possibly much less IO.  (Or possibly not,
 
648
    # if the directories are very large...)
 
649
    @display_command
589
650
    def run(self, show_ids=False):
590
 
        b = find_branch('.')
 
651
        b = Branch.open_containing('.')[0]
591
652
        old = b.basis_tree()
592
653
        new = b.working_tree()
593
 
 
594
 
        ## TODO: Much more efficient way to do this: read in new
595
 
        ## directories with readdir, rather than stating each one.  Same
596
 
        ## level of effort but possibly much less IO.  (Or possibly not,
597
 
        ## if the directories are very large...)
598
 
 
599
654
        for path, ie in old.inventory.iter_entries():
600
655
            if not new.has_id(ie.file_id):
601
656
                if show_ids:
607
662
class cmd_modified(Command):
608
663
    """List files modified in working tree."""
609
664
    hidden = True
 
665
    @display_command
610
666
    def run(self):
611
667
        from bzrlib.delta import compare_trees
612
668
 
613
 
        b = find_branch('.')
 
669
        b = Branch.open_containing('.')[0]
614
670
        td = compare_trees(b.basis_tree(), b.working_tree())
615
671
 
616
 
        for path, id, kind in td.modified:
 
672
        for path, id, kind, text_modified, meta_modified in td.modified:
617
673
            print path
618
674
 
619
675
 
621
677
class cmd_added(Command):
622
678
    """List files added in working tree."""
623
679
    hidden = True
 
680
    @display_command
624
681
    def run(self):
625
 
        b = find_branch('.')
 
682
        b = Branch.open_containing('.')[0]
626
683
        wt = b.working_tree()
627
684
        basis_inv = b.basis_tree().inventory
628
685
        inv = wt.inventory
642
699
    The root is the nearest enclosing directory with a .bzr control
643
700
    directory."""
644
701
    takes_args = ['filename?']
 
702
    @display_command
645
703
    def run(self, filename=None):
646
704
        """Print the branch root."""
647
 
        b = find_branch(filename)
648
 
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
 
705
        b = Branch.open_containing(filename)[0]
 
706
        print b.base
649
707
 
650
708
 
651
709
class cmd_log(Command):
654
712
    To request a range of logs, you can use the command -r begin:end
655
713
    -r revision requests a specific revision, -r :end or -r begin: are
656
714
    also valid.
657
 
 
658
 
    --message allows you to give a regular expression, which will be evaluated
659
 
    so that only matching entries will be displayed.
660
 
 
661
 
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
662
 
  
663
715
    """
664
716
 
 
717
    # TODO: Make --revision support uuid: and hash: [future tag:] notation.
 
718
 
665
719
    takes_args = ['filename?']
666
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision',
667
 
                     'long', 'message', 'short',]
668
 
    
 
720
    takes_options = [Option('forward', 
 
721
                            help='show from oldest to newest'),
 
722
                     'timezone', 'verbose', 
 
723
                     'show-ids', 'revision',
 
724
                     Option('line', help='format with one line per revision'),
 
725
                     'long', 
 
726
                     Option('message',
 
727
                            help='show revisions whose message matches this regexp',
 
728
                            type=str),
 
729
                     Option('short', help='use moderately short format'),
 
730
                     ]
 
731
    @display_command
669
732
    def run(self, filename=None, timezone='original',
670
733
            verbose=False,
671
734
            show_ids=False,
673
736
            revision=None,
674
737
            message=None,
675
738
            long=False,
676
 
            short=False):
 
739
            short=False,
 
740
            line=False):
677
741
        from bzrlib.log import log_formatter, show_log
678
742
        import codecs
679
 
 
 
743
        assert message is None or isinstance(message, basestring), \
 
744
            "invalid message argument %r" % message
680
745
        direction = (forward and 'forward') or 'reverse'
681
746
        
682
747
        if filename:
683
 
            b = find_branch(filename)
684
 
            fp = b.relpath(filename)
685
 
            if fp:
 
748
            b, fp = Branch.open_containing(filename)
 
749
            if fp != '':
686
750
                file_id = b.read_working_inventory().path2id(fp)
687
751
            else:
688
752
                file_id = None  # points to branch root
689
753
        else:
690
 
            b = find_branch('.')
 
754
            b, relpath = Branch.open_containing('.')
691
755
            file_id = None
692
756
 
693
757
        if revision is None:
694
758
            rev1 = None
695
759
            rev2 = None
696
760
        elif len(revision) == 1:
697
 
            rev1 = rev2 = b.get_revision_info(revision[0])[0]
 
761
            rev1 = rev2 = revision[0].in_history(b).revno
698
762
        elif len(revision) == 2:
699
 
            rev1 = b.get_revision_info(revision[0])[0]
700
 
            rev2 = b.get_revision_info(revision[1])[0]
 
763
            rev1 = revision[0].in_history(b).revno
 
764
            rev2 = revision[1].in_history(b).revno
701
765
        else:
702
766
            raise BzrCommandError('bzr log --revision takes one or two values.')
703
767
 
712
776
        # in e.g. the default C locale.
713
777
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
714
778
 
715
 
        if not short:
716
 
            log_format = 'long'
717
 
        else:
 
779
        log_format = 'long'
 
780
        if short:
718
781
            log_format = 'short'
 
782
        if line:
 
783
            log_format = 'line'
719
784
        lf = log_formatter(log_format,
720
785
                           show_ids=show_ids,
721
786
                           to_file=outf,
738
803
    A more user-friendly interface is "bzr log FILE"."""
739
804
    hidden = True
740
805
    takes_args = ["filename"]
 
806
    @display_command
741
807
    def run(self, filename):
742
 
        b = find_branch(filename)
 
808
        b, relpath = Branch.open_containing(filename)[0]
743
809
        inv = b.read_working_inventory()
744
 
        file_id = inv.path2id(b.relpath(filename))
 
810
        file_id = inv.path2id(relpath)
745
811
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
746
812
            print "%6d %s" % (revno, what)
747
813
 
748
814
 
749
815
class cmd_ls(Command):
750
816
    """List files in a tree.
751
 
 
752
 
    TODO: Take a revision or remote path and list that tree instead.
753
817
    """
 
818
    # TODO: Take a revision or remote path and list that tree instead.
754
819
    hidden = True
 
820
    @display_command
755
821
    def run(self, revision=None, verbose=False):
756
 
        b = find_branch('.')
 
822
        b, relpath = Branch.open_containing('.')[0]
757
823
        if revision == None:
758
824
            tree = b.working_tree()
759
825
        else:
760
 
            tree = b.revision_tree(b.lookup_revision(revision))
761
 
 
762
 
        for fp, fc, kind, fid in tree.list_files():
 
826
            tree = b.revision_tree(revision.in_history(b).rev_id)
 
827
        for fp, fc, kind, fid, entry in tree.list_files():
763
828
            if verbose:
764
 
                if kind == 'directory':
765
 
                    kindch = '/'
766
 
                elif kind == 'file':
767
 
                    kindch = ''
768
 
                else:
769
 
                    kindch = '???'
770
 
 
 
829
                kindch = entry.kind_character()
771
830
                print '%-8s %s%s' % (fc, fp, kindch)
772
831
            else:
773
832
                print fp
776
835
 
777
836
class cmd_unknowns(Command):
778
837
    """List unknown files."""
 
838
    @display_command
779
839
    def run(self):
780
840
        from bzrlib.osutils import quotefn
781
 
        for f in find_branch('.').unknowns():
 
841
        for f in Branch.open_containing('.')[0].unknowns():
782
842
            print quotefn(f)
783
843
 
784
844
 
789
849
    To remove patterns from the ignore list, edit the .bzrignore file.
790
850
 
791
851
    If the pattern contains a slash, it is compared to the whole path
792
 
    from the branch root.  Otherwise, it is comapred to only the last
793
 
    component of the path.
 
852
    from the branch root.  Otherwise, it is compared to only the last
 
853
    component of the path.  To match a file only in the root directory,
 
854
    prepend './'.
794
855
 
795
856
    Ignore patterns are case-insensitive on case-insensitive systems.
796
857
 
800
861
        bzr ignore ./Makefile
801
862
        bzr ignore '*.class'
802
863
    """
 
864
    # TODO: Complain if the filename is absolute
803
865
    takes_args = ['name_pattern']
804
866
    
805
867
    def run(self, name_pattern):
806
868
        from bzrlib.atomicfile import AtomicFile
807
869
        import os.path
808
870
 
809
 
        b = find_branch('.')
 
871
        b, relpath = Branch.open_containing('.')
810
872
        ifn = b.abspath('.bzrignore')
811
873
 
812
874
        if os.path.exists(ifn):
845
907
    """List ignored files and the patterns that matched them.
846
908
 
847
909
    See also: bzr ignore"""
 
910
    @display_command
848
911
    def run(self):
849
 
        tree = find_branch('.').working_tree()
850
 
        for path, file_class, kind, file_id in tree.list_files():
 
912
        tree = Branch.open_containing('.')[0].working_tree()
 
913
        for path, file_class, kind, file_id, entry in tree.list_files():
851
914
            if file_class != 'I':
852
915
                continue
853
916
            ## XXX: Slightly inefficient since this was already calculated
864
927
    hidden = True
865
928
    takes_args = ['revno']
866
929
    
 
930
    @display_command
867
931
    def run(self, revno):
868
932
        try:
869
933
            revno = int(revno)
870
934
        except ValueError:
871
935
            raise BzrCommandError("not a valid revision-number: %r" % revno)
872
936
 
873
 
        print find_branch('.').lookup_revision(revno)
 
937
        print Branch.open_containing('.')[0].get_rev_id(revno)
874
938
 
875
939
 
876
940
class cmd_export(Command):
889
953
    takes_options = ['revision', 'format', 'root']
890
954
    def run(self, dest, revision=None, format=None, root=None):
891
955
        import os.path
892
 
        b = find_branch('.')
 
956
        b = Branch.open_containing('.')[0]
893
957
        if revision is None:
894
958
            rev_id = b.last_revision()
895
959
        else:
896
960
            if len(revision) != 1:
897
961
                raise BzrError('bzr export --revision takes exactly 1 argument')
898
 
            revno, rev_id = b.get_revision_info(revision[0])
 
962
            rev_id = revision[0].in_history(b).rev_id
899
963
        t = b.revision_tree(rev_id)
900
 
        root, ext = os.path.splitext(dest)
 
964
        arg_root, ext = os.path.splitext(os.path.basename(dest))
 
965
        if ext in ('.gz', '.bz2'):
 
966
            new_root, new_ext = os.path.splitext(arg_root)
 
967
            if new_ext == '.tar':
 
968
                arg_root = new_root
 
969
                ext = new_ext + ext
 
970
        if root is None:
 
971
            root = arg_root
901
972
        if not format:
902
973
            if ext in (".tar",):
903
974
                format = "tar"
904
 
            elif ext in (".gz", ".tgz"):
 
975
            elif ext in (".tar.gz", ".tgz"):
905
976
                format = "tgz"
906
 
            elif ext in (".bz2", ".tbz2"):
 
977
            elif ext in (".tar.bz2", ".tbz2"):
907
978
                format = "tbz2"
908
979
            else:
909
980
                format = "dir"
916
987
    takes_options = ['revision']
917
988
    takes_args = ['filename']
918
989
 
 
990
    @display_command
919
991
    def run(self, filename, revision=None):
920
 
        if revision == None:
 
992
        if revision is None:
921
993
            raise BzrCommandError("bzr cat requires a revision number")
922
994
        elif len(revision) != 1:
923
995
            raise BzrCommandError("bzr cat --revision takes exactly one number")
924
 
        b = find_branch('.')
925
 
        b.print_file(b.relpath(filename), revision[0])
 
996
        b, relpath = Branch.open_containing(filename)
 
997
        b.print_file(relpath, revision[0].in_history(b).revno)
926
998
 
927
999
 
928
1000
class cmd_local_time_offset(Command):
929
1001
    """Show the offset in seconds from GMT to local time."""
930
1002
    hidden = True    
 
1003
    @display_command
931
1004
    def run(self):
932
1005
        print bzrlib.osutils.local_time_offset()
933
1006
 
945
1018
    A selected-file commit may fail in some cases where the committed
946
1019
    tree would be invalid, such as trying to commit a file in a
947
1020
    newly-added directory that is not itself committed.
948
 
 
949
 
    TODO: Run hooks on tree to-be-committed, and after commit.
950
 
 
951
 
    TODO: Strict commit that fails if there are unknown or deleted files.
952
1021
    """
 
1022
    # TODO: Run hooks on tree to-be-committed, and after commit.
 
1023
 
 
1024
    # TODO: Strict commit that fails if there are deleted files.
 
1025
    #       (what does "deleted files" mean ??)
 
1026
 
 
1027
    # TODO: Give better message for -s, --summary, used by tla people
 
1028
 
 
1029
    # XXX: verbose currently does nothing
 
1030
 
953
1031
    takes_args = ['selected*']
954
 
    takes_options = ['message', 'file', 'verbose', 'unchanged']
 
1032
    takes_options = ['message', 'verbose', 
 
1033
                     Option('unchanged',
 
1034
                            help='commit even if nothing has changed'),
 
1035
                     Option('file', type=str, 
 
1036
                            argname='msgfile',
 
1037
                            help='file containing commit message'),
 
1038
                     Option('strict',
 
1039
                            help="refuse to commit if there are unknown "
 
1040
                            "files in the working tree."),
 
1041
                     ]
955
1042
    aliases = ['ci', 'checkin']
956
1043
 
957
 
    # TODO: Give better message for -s, --summary, used by tla people
958
 
 
959
 
    # XXX: verbose currently does nothing
960
 
    
961
1044
    def run(self, message=None, file=None, verbose=True, selected_list=None,
962
 
            unchanged=False):
963
 
        from bzrlib.errors import PointlessCommit
 
1045
            unchanged=False, strict=False):
 
1046
        from bzrlib.errors import (PointlessCommit, ConflictsInTree,
 
1047
                StrictCommitFailed)
964
1048
        from bzrlib.msgeditor import edit_commit_message
965
1049
        from bzrlib.status import show_status
966
1050
        from cStringIO import StringIO
967
1051
 
968
 
        b = find_branch('.')
 
1052
        b = Branch.open_containing('.')[0]
 
1053
        tree = WorkingTree(b.base, b)
969
1054
        if selected_list:
970
 
            selected_list = [b.relpath(s) for s in selected_list]
971
 
            
972
 
        if not message and not file:
 
1055
            selected_list = [tree.relpath(s) for s in selected_list]
 
1056
        if message is None and not file:
973
1057
            catcher = StringIO()
974
1058
            show_status(b, specific_files=selected_list,
975
1059
                        to_file=catcher)
976
1060
            message = edit_commit_message(catcher.getvalue())
977
 
            
 
1061
 
978
1062
            if message is None:
979
1063
                raise BzrCommandError("please specify a commit message"
980
1064
                                      " with either --message or --file")
985
1069
            import codecs
986
1070
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
987
1071
 
 
1072
        if message == "":
 
1073
                raise BzrCommandError("empty commit message specified")
 
1074
            
988
1075
        try:
989
 
            b.commit(message,
990
 
                     specific_files=selected_list,
991
 
                     allow_pointless=unchanged)
 
1076
            b.commit(message, specific_files=selected_list,
 
1077
                     allow_pointless=unchanged, strict=strict)
992
1078
        except PointlessCommit:
993
1079
            # FIXME: This should really happen before the file is read in;
994
1080
            # perhaps prepare the commit; get the message; then actually commit
995
1081
            raise BzrCommandError("no changes to commit",
996
1082
                                  ["use --unchanged to commit anyhow"])
 
1083
        except ConflictsInTree:
 
1084
            raise BzrCommandError("Conflicts detected in working tree.  "
 
1085
                'Use "bzr conflicts" to list, "bzr resolve FILE" to resolve.')
 
1086
        except StrictCommitFailed:
 
1087
            raise BzrCommandError("Commit refused because there are unknown "
 
1088
                                  "files in the working tree.")
997
1089
 
998
1090
 
999
1091
class cmd_check(Command):
1001
1093
 
1002
1094
    This command checks various invariants about the branch storage to
1003
1095
    detect data corruption or bzr bugs.
1004
 
 
1005
 
    If given the --update flag, it will update some optional fields
1006
 
    to help ensure data consistency.
1007
1096
    """
1008
1097
    takes_args = ['dir?']
 
1098
    takes_options = ['verbose']
1009
1099
 
1010
 
    def run(self, dir='.'):
 
1100
    def run(self, dir='.', verbose=False):
1011
1101
        from bzrlib.check import check
1012
 
 
1013
 
        check(find_branch(dir))
 
1102
        check(Branch.open_containing(dir)[0], verbose)
1014
1103
 
1015
1104
 
1016
1105
class cmd_scan_cache(Command):
1038
1127
 
1039
1128
    The check command or bzr developers may sometimes advise you to run
1040
1129
    this command.
 
1130
 
 
1131
    This version of this command upgrades from the full-text storage
 
1132
    used by bzr 0.0.8 and earlier to the weave format (v5).
1041
1133
    """
1042
1134
    takes_args = ['dir?']
1043
1135
 
1044
1136
    def run(self, dir='.'):
1045
1137
        from bzrlib.upgrade import upgrade
1046
 
        upgrade(find_branch(dir))
1047
 
 
 
1138
        upgrade(dir)
1048
1139
 
1049
1140
 
1050
1141
class cmd_whoami(Command):
1051
1142
    """Show bzr user id."""
1052
1143
    takes_options = ['email']
1053
1144
    
 
1145
    @display_command
1054
1146
    def run(self, email=False):
1055
1147
        try:
1056
 
            b = bzrlib.branch.find_branch('.')
1057
 
        except:
1058
 
            b = None
 
1148
            b = bzrlib.branch.Branch.open_containing('.')[0]
 
1149
            config = bzrlib.config.BranchConfig(b)
 
1150
        except NotBranchError:
 
1151
            config = bzrlib.config.GlobalConfig()
1059
1152
        
1060
1153
        if email:
1061
 
            print bzrlib.osutils.user_email(b)
 
1154
            print config.user_email()
1062
1155
        else:
1063
 
            print bzrlib.osutils.username(b)
 
1156
            print config.username()
1064
1157
 
1065
1158
 
1066
1159
class cmd_selftest(Command):
1067
 
    """Run internal test suite"""
 
1160
    """Run internal test suite.
 
1161
    
 
1162
    This creates temporary test directories in the working directory,
 
1163
    but not existing data is affected.  These directories are deleted
 
1164
    if the tests pass, or left behind to help in debugging if they
 
1165
    fail.
 
1166
    
 
1167
    If arguments are given, they are regular expressions that say
 
1168
    which tests should run.
 
1169
    """
 
1170
    # TODO: --list should give a list of all available tests
1068
1171
    hidden = True
1069
 
    takes_options = ['verbose', 'pattern']
1070
 
    def run(self, verbose=False, pattern=".*"):
 
1172
    takes_args = ['testspecs*']
 
1173
    takes_options = ['verbose', 
 
1174
                     Option('one', help='stop when one test fails'),
 
1175
                    ]
 
1176
 
 
1177
    def run(self, testspecs_list=None, verbose=False, one=False):
1071
1178
        import bzrlib.ui
1072
1179
        from bzrlib.selftest import selftest
1073
1180
        # we don't want progress meters from the tests to go to the
1077
1184
        bzrlib.trace.info('running tests...')
1078
1185
        try:
1079
1186
            bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1080
 
            result = selftest(verbose=verbose, pattern=pattern)
 
1187
            if testspecs_list is not None:
 
1188
                pattern = '|'.join(testspecs_list)
 
1189
            else:
 
1190
                pattern = ".*"
 
1191
            result = selftest(verbose=verbose, 
 
1192
                              pattern=pattern,
 
1193
                              stop_on_failure=one)
1081
1194
            if result:
1082
1195
                bzrlib.trace.info('tests passed')
1083
1196
            else:
1103
1216
 
1104
1217
class cmd_version(Command):
1105
1218
    """Show version of bzr."""
 
1219
    @display_command
1106
1220
    def run(self):
1107
1221
        show_version()
1108
1222
 
1109
1223
class cmd_rocks(Command):
1110
1224
    """Statement of optimism."""
1111
1225
    hidden = True
 
1226
    @display_command
1112
1227
    def run(self):
1113
1228
        print "it sure does!"
1114
1229
 
1115
1230
 
1116
1231
class cmd_find_merge_base(Command):
1117
1232
    """Find and print a base revision for merging two branches.
1118
 
 
1119
 
    TODO: Options to specify revisions on either side, as if
1120
 
          merging only part of the history.
1121
1233
    """
 
1234
    # TODO: Options to specify revisions on either side, as if
 
1235
    #       merging only part of the history.
1122
1236
    takes_args = ['branch', 'other']
1123
1237
    hidden = True
1124
1238
    
 
1239
    @display_command
1125
1240
    def run(self, branch, other):
1126
1241
        from bzrlib.revision import common_ancestor, MultipleRevisionSources
1127
1242
        
1128
 
        branch1 = find_branch(branch)
1129
 
        branch2 = find_branch(other)
 
1243
        branch1 = Branch.open_containing(branch)[0]
 
1244
        branch2 = Branch.open_containing(other)[0]
1130
1245
 
1131
1246
        history_1 = branch1.revision_history()
1132
1247
        history_2 = branch2.revision_history()
1181
1296
    --force is given.
1182
1297
    """
1183
1298
    takes_args = ['branch?']
1184
 
    takes_options = ['revision', 'force', 'merge-type']
 
1299
    takes_options = ['revision', 'force', 'merge-type', 
 
1300
                     Option('show-base', help="Show base revision text in "
 
1301
                            "conflicts")]
1185
1302
 
1186
 
    def run(self, branch='.', revision=None, force=False, 
1187
 
            merge_type=None):
 
1303
    def run(self, branch=None, revision=None, force=False, merge_type=None,
 
1304
            show_base=False):
1188
1305
        from bzrlib.merge import merge
1189
1306
        from bzrlib.merge_core import ApplyMerge3
1190
1307
        if merge_type is None:
1191
1308
            merge_type = ApplyMerge3
1192
 
 
 
1309
        if branch is None:
 
1310
            branch = Branch.open_containing('.')[0].get_parent()
 
1311
            if branch is None:
 
1312
                raise BzrCommandError("No merge location known or specified.")
 
1313
            else:
 
1314
                print "Using saved location: %s" % branch 
1193
1315
        if revision is None or len(revision) < 1:
1194
1316
            base = [None, None]
1195
1317
            other = [branch, -1]
1196
1318
        else:
1197
1319
            if len(revision) == 1:
1198
 
                other = [branch, revision[0]]
1199
1320
                base = [None, None]
 
1321
                other_branch = Branch.open_containing(branch)[0]
 
1322
                revno = revision[0].in_history(other_branch).revno
 
1323
                other = [branch, revno]
1200
1324
            else:
1201
1325
                assert len(revision) == 2
1202
1326
                if None in revision:
1203
1327
                    raise BzrCommandError(
1204
1328
                        "Merge doesn't permit that revision specifier.")
1205
 
                base = [branch, revision[0]]
1206
 
                other = [branch, revision[1]]
 
1329
                b = Branch.open_containing(branch)[0]
 
1330
 
 
1331
                base = [branch, revision[0].in_history(b).revno]
 
1332
                other = [branch, revision[1].in_history(b).revno]
1207
1333
 
1208
1334
        try:
1209
 
            merge(other, base, check_clean=(not force), merge_type=merge_type)
 
1335
            conflict_count = merge(other, base, check_clean=(not force),
 
1336
                                   merge_type=merge_type,
 
1337
                                   show_base=show_base)
 
1338
            if conflict_count != 0:
 
1339
                return 1
 
1340
            else:
 
1341
                return 0
1210
1342
        except bzrlib.errors.AmbiguousBase, e:
1211
1343
            m = ("sorry, bzr can't determine the right merge base yet\n"
1212
1344
                 "candidates are:\n  "
1230
1362
 
1231
1363
    def run(self, revision=None, no_backup=False, file_list=None):
1232
1364
        from bzrlib.merge import merge
1233
 
        from bzrlib.branch import Branch
1234
1365
        from bzrlib.commands import parse_spec
1235
1366
 
1236
1367
        if file_list is not None:
1237
1368
            if len(file_list) == 0:
1238
1369
                raise BzrCommandError("No files specified")
1239
1370
        if revision is None:
1240
 
            revision = [-1]
 
1371
            revno = -1
1241
1372
        elif len(revision) != 1:
1242
1373
            raise BzrCommandError('bzr revert --revision takes exactly 1 argument')
1243
 
        merge(('.', revision[0]), parse_spec('.'),
 
1374
        else:
 
1375
            b = Branch.open_containing('.')[0]
 
1376
            revno = revision[0].in_history(b).revno
 
1377
        merge(('.', revno), parse_spec('.'),
1244
1378
              check_clean=False,
1245
1379
              ignore_zero=True,
1246
1380
              backup_files=not no_backup,
1247
1381
              file_list=file_list)
1248
1382
        if not file_list:
1249
 
            Branch('.').set_pending_merges([])
 
1383
            Branch.open_containing('.')[0].set_pending_merges([])
1250
1384
 
1251
1385
 
1252
1386
class cmd_assert_fail(Command):
1264
1398
    takes_args = ['topic?']
1265
1399
    aliases = ['?']
1266
1400
    
 
1401
    @display_command
1267
1402
    def run(self, topic=None, long=False):
1268
1403
        import help
1269
1404
        if topic is None and long:
1279
1414
    aliases = ['s-c']
1280
1415
    hidden = True
1281
1416
    
 
1417
    @display_command
1282
1418
    def run(self, context=None):
1283
1419
        import shellcomplete
1284
1420
        shellcomplete.shellcomplete(context)
1311
1447
    # unknown options are parsed as booleans
1312
1448
    takes_options = ['verbose', 'quiet']
1313
1449
 
 
1450
    @display_command
1314
1451
    def run(self, remote=None, verbose=False, quiet=False):
1315
1452
        from bzrlib.errors import BzrCommandError
1316
1453
        from bzrlib.missing import show_missing
1318
1455
        if verbose and quiet:
1319
1456
            raise BzrCommandError('Cannot pass both quiet and verbose')
1320
1457
 
1321
 
        b = find_branch('.')
 
1458
        b = Branch.open_containing('.')[0]
1322
1459
        parent = b.get_parent()
1323
1460
        if remote is None:
1324
1461
            if parent is None:
1328
1465
                    print "Using last location: %s" % parent
1329
1466
                remote = parent
1330
1467
        elif parent is None:
1331
 
            # We only update x-pull if it did not exist, missing should not change the parent
1332
 
            b.controlfile('x-pull', 'wb').write(remote + '\n')
1333
 
        br_remote = find_branch(remote)
1334
 
 
 
1468
            # We only update parent if it did not exist, missing
 
1469
            # should not change the parent
 
1470
            b.set_parent(remote)
 
1471
        br_remote = Branch.open_containing(remote)[0]
1335
1472
        return show_missing(b, br_remote, verbose=verbose, quiet=quiet)
1336
1473
 
1337
1474
 
1338
 
 
1339
1475
class cmd_plugins(Command):
1340
1476
    """List plugins"""
1341
1477
    hidden = True
 
1478
    @display_command
1342
1479
    def run(self):
1343
1480
        import bzrlib.plugin
1344
1481
        from inspect import getdoc
1355
1492
                print '\t', d.split('\n')[0]
1356
1493
 
1357
1494
 
 
1495
class cmd_testament(Command):
 
1496
    """Show testament (signing-form) of a revision."""
 
1497
    takes_options = ['revision', 'long']
 
1498
    takes_args = ['branch?']
 
1499
    @display_command
 
1500
    def run(self, branch='.', revision=None, long=False):
 
1501
        from bzrlib.testament import Testament
 
1502
        b = Branch.open_containing(branch)[0]
 
1503
        b.lock_read()
 
1504
        try:
 
1505
            if revision is None:
 
1506
                rev_id = b.last_revision()
 
1507
            else:
 
1508
                rev_id = revision[0].in_history(b).rev_id
 
1509
            t = Testament.from_revision(b, rev_id)
 
1510
            if long:
 
1511
                sys.stdout.writelines(t.as_text_lines())
 
1512
            else:
 
1513
                sys.stdout.write(t.as_short_text())
 
1514
        finally:
 
1515
            b.unlock()
 
1516
 
 
1517
 
 
1518
class cmd_annotate(Command):
 
1519
    """Show the origin of each line in a file.
 
1520
 
 
1521
    This prints out the given file with an annotation on the left side
 
1522
    indicating which revision, author and date introduced the change.
 
1523
 
 
1524
    If the origin is the same for a run of consecutive lines, it is 
 
1525
    shown only at the top, unless the --all option is given.
 
1526
    """
 
1527
    # TODO: annotate directories; showing when each file was last changed
 
1528
    # TODO: annotate a previous version of a file
 
1529
    # TODO: if the working copy is modified, show annotations on that 
 
1530
    #       with new uncommitted lines marked
 
1531
    aliases = ['blame', 'praise']
 
1532
    takes_args = ['filename']
 
1533
    takes_options = [Option('all', help='show annotations on all lines'),
 
1534
                     Option('long', help='show date in annotations'),
 
1535
                     ]
 
1536
 
 
1537
    @display_command
 
1538
    def run(self, filename, all=False, long=False):
 
1539
        from bzrlib.annotate import annotate_file
 
1540
        b, relpath = Branch.open_containing(filename)
 
1541
        b.lock_read()
 
1542
        try:
 
1543
            tree = WorkingTree(b.base, b)
 
1544
            tree = b.revision_tree(b.last_revision())
 
1545
            file_id = tree.inventory.path2id(relpath)
 
1546
            file_version = tree.inventory[file_id].revision
 
1547
            annotate_file(b, file_version, file_id, long, all, sys.stdout)
 
1548
        finally:
 
1549
            b.unlock()
 
1550
 
 
1551
 
 
1552
class cmd_re_sign(Command):
 
1553
    """Create a digital signature for an existing revision."""
 
1554
    # TODO be able to replace existing ones.
 
1555
 
 
1556
    hidden = True # is this right ?
 
1557
    takes_args = ['revision_id?']
 
1558
    takes_options = ['revision']
 
1559
    
 
1560
    def run(self, revision_id=None, revision=None):
 
1561
        import bzrlib.config as config
 
1562
        import bzrlib.gpg as gpg
 
1563
        if revision_id is not None and revision is not None:
 
1564
            raise BzrCommandError('You can only supply one of revision_id or --revision')
 
1565
        if revision_id is None and revision is None:
 
1566
            raise BzrCommandError('You must supply either --revision or a revision_id')
 
1567
        b = Branch.open_containing('.')[0]
 
1568
        gpg_strategy = gpg.GPGStrategy(config.BranchConfig(b))
 
1569
        if revision_id is not None:
 
1570
            b.sign_revision(revision_id, gpg_strategy)
 
1571
        elif revision is not None:
 
1572
            for rev in revision:
 
1573
                if rev is None:
 
1574
                    raise BzrCommandError('You cannot specify a NULL revision.')
 
1575
                revno, rev_id = rev.in_history(b)
 
1576
                b.sign_revision(rev_id, gpg_strategy)
 
1577
 
 
1578
 
 
1579
# these get imported and then picked up by the scan for cmd_*
 
1580
# TODO: Some more consistent way to split command definitions across files;
 
1581
# we do need to load at least some information about them to know of 
 
1582
# aliases.
 
1583
from bzrlib.conflicts import cmd_resolve, cmd_conflicts