~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/builtins.py

[merge] robertc's integration, updated tests to check for retcode=3

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.revision import common_ancestor
 
30
import bzrlib.errors as errors
 
31
from bzrlib.errors import (BzrError, BzrCheckError, BzrCommandError, 
 
32
                           NotBranchError, DivergedBranches, NotConflicted,
 
33
                           NoSuchFile, NoWorkingTree)
 
34
from bzrlib.option import Option
 
35
from bzrlib.revisionspec import RevisionSpec
22
36
import bzrlib.trace
23
37
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
28
 
 
 
38
from bzrlib.workingtree import WorkingTree
 
39
 
 
40
 
 
41
def branch_files(file_list, default_branch='.'):
 
42
    try:
 
43
        return inner_branch_files(file_list, default_branch)
 
44
    except NotBranchError:
 
45
        raise BzrCommandError("%s is not in the same branch as %s" %
 
46
                             (filename, file_list[0]))
 
47
 
 
48
def inner_branch_files(file_list, default_branch='.'):
 
49
    """\
 
50
    Return a branch and list of branch-relative paths.
 
51
    If supplied file_list is empty or None, the branch default will be used,
 
52
    and returned file_list will match the original.
 
53
    """
 
54
    if file_list is None or len(file_list) == 0:
 
55
        return Branch.open_containing(default_branch)[0], file_list
 
56
    b = Branch.open_containing(file_list[0])[0]
 
57
    
 
58
    # note that if this is a remote branch, we would want
 
59
    # relpath against the transport. RBC 20051018
 
60
    # Most branch ops can't meaningfully operate on files in remote branches;
 
61
    # the above comment was in cmd_status.  ADHB 20051026
 
62
    tree = WorkingTree(b.base, b)
 
63
    new_list = []
 
64
    for filename in file_list:
 
65
        new_list.append(tree.relpath(filename))
 
66
    return b, new_list
 
67
 
 
68
 
 
69
# TODO: Make sure no commands unconditionally use the working directory as a
 
70
# branch.  If a filename argument is used, the first of them should be used to
 
71
# specify the branch.  (Perhaps this can be factored out into some kind of
 
72
# Argument class, representing a file in a branch, where the first occurrence
 
73
# opens the branch?)
29
74
 
30
75
class cmd_status(Command):
31
76
    """Display status summary.
63
108
    files or directories is reported.  If a directory is given, status
64
109
    is reported for everything inside that directory.
65
110
 
66
 
    If a revision is specified, the changes since that revision are shown.
 
111
    If a revision argument is given, the status is calculated against
 
112
    that revision, or between two revisions if two are provided.
67
113
    """
 
114
    
 
115
    # TODO: --no-recurse, --recurse options
 
116
    
68
117
    takes_args = ['file*']
69
118
    takes_options = ['all', 'show-ids', 'revision']
70
119
    aliases = ['st', 'stat']
71
120
    
72
 
    def run(self, all=False, show_ids=False, file_list=None):
73
 
        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 == ['']:
79
 
                file_list = None
80
 
        else:
81
 
            b = find_branch('.')
 
121
    @display_command
 
122
    def run(self, all=False, show_ids=False, file_list=None, revision=None):
 
123
        b, file_list = branch_files(file_list)
82
124
            
83
125
        from bzrlib.status import show_status
84
126
        show_status(b, show_unchanged=all, show_ids=show_ids,
85
 
                    specific_files=file_list)
 
127
                    specific_files=file_list, revision=revision)
86
128
 
87
129
 
88
130
class cmd_cat_revision(Command):
89
 
    """Write out metadata for a revision."""
 
131
    """Write out metadata for a revision.
 
132
    
 
133
    The revision to print can either be specified by a specific
 
134
    revision identifier, or you can use --revision.
 
135
    """
90
136
 
91
137
    hidden = True
92
 
    takes_args = ['revision_id']
 
138
    takes_args = ['revision_id?']
 
139
    takes_options = ['revision']
93
140
    
94
 
    def run(self, revision_id):
95
 
        b = find_branch('.')
96
 
        sys.stdout.write(b.get_revision_xml_file(revision_id).read())
 
141
    @display_command
 
142
    def run(self, revision_id=None, revision=None):
97
143
 
 
144
        if revision_id is not None and revision is not None:
 
145
            raise BzrCommandError('You can only supply one of revision_id or --revision')
 
146
        if revision_id is None and revision is None:
 
147
            raise BzrCommandError('You must supply either --revision or a revision_id')
 
148
        b = Branch.open_containing('.')[0]
 
149
        if revision_id is not None:
 
150
            sys.stdout.write(b.get_revision_xml_file(revision_id).read())
 
151
        elif revision is not None:
 
152
            for rev in revision:
 
153
                if rev is None:
 
154
                    raise BzrCommandError('You cannot specify a NULL revision.')
 
155
                revno, rev_id = rev.in_history(b)
 
156
                sys.stdout.write(b.get_revision_xml_file(rev_id).read())
 
157
    
98
158
 
99
159
class cmd_revno(Command):
100
160
    """Show current revision number.
101
161
 
102
162
    This is equal to the number of revisions on this branch."""
 
163
    @display_command
103
164
    def run(self):
104
 
        print find_branch('.').revno()
 
165
        print Branch.open_containing('.')[0].revno()
105
166
 
106
167
 
107
168
class cmd_revision_info(Command):
110
171
    hidden = True
111
172
    takes_args = ['revision_info*']
112
173
    takes_options = ['revision']
113
 
    def run(self, revision=None, revision_info_list=None):
114
 
        from bzrlib.branch import find_branch
 
174
    @display_command
 
175
    def run(self, revision=None, revision_info_list=[]):
115
176
 
116
177
        revs = []
117
178
        if revision is not None:
118
179
            revs.extend(revision)
119
180
        if revision_info_list is not None:
120
 
            revs.extend(revision_info_list)
 
181
            for rev in revision_info_list:
 
182
                revs.append(RevisionSpec(rev))
121
183
        if len(revs) == 0:
122
184
            raise BzrCommandError('You must supply a revision identifier')
123
185
 
124
 
        b = find_branch('.')
 
186
        b = Branch.open_containing('.')[0]
125
187
 
126
188
        for rev in revs:
127
 
            print '%4d %s' % b.get_revision_info(rev)
 
189
            revinfo = rev.in_history(b)
 
190
            if revinfo.revno is None:
 
191
                print '     %s' % revinfo.rev_id
 
192
            else:
 
193
                print '%4d %s' % (revinfo.revno, revinfo.rev_id)
128
194
 
129
195
    
130
196
class cmd_add(Command):
145
211
    Therefore simply saying 'bzr add' will version all files that
146
212
    are currently unknown.
147
213
 
148
 
    TODO: Perhaps adding a file whose directly is not versioned should
149
 
    recursively add that parent, rather than giving an error?
 
214
    Adding a file whose parent directory is not versioned will
 
215
    implicitly add the parent, and so on up to the root. This means
 
216
    you should never need to explictly add a directory, they'll just
 
217
    get added when you add a file in the directory.
150
218
    """
151
219
    takes_args = ['file*']
152
 
    takes_options = ['verbose', 'no-recurse']
 
220
    takes_options = ['no-recurse', 'quiet']
153
221
    
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
 
 
 
222
    def run(self, file_list, no_recurse=False, quiet=False):
 
223
        from bzrlib.add import smart_add, add_reporter_print, add_reporter_null
 
224
        if quiet:
 
225
            reporter = add_reporter_null
 
226
        else:
 
227
            reporter = add_reporter_print
 
228
        smart_add(file_list, not no_recurse, reporter)
159
229
 
160
230
 
161
231
class cmd_mkdir(Command):
170
240
        
171
241
        for d in dir_list:
172
242
            os.mkdir(d)
173
 
            if not b:
174
 
                b = find_branch(d)
175
 
            b.add([d])
 
243
            b, dd = Branch.open_containing(d)
 
244
            b.add([dd])
176
245
            print 'added', d
177
246
 
178
247
 
181
250
    takes_args = ['filename']
182
251
    hidden = True
183
252
    
 
253
    @display_command
184
254
    def run(self, filename):
185
 
        print find_branch(filename).relpath(filename)
186
 
 
 
255
        branch, relpath = Branch.open_containing(filename)
 
256
        print relpath
187
257
 
188
258
 
189
259
class cmd_inventory(Command):
190
260
    """Show inventory of the current working copy or a revision."""
191
261
    takes_options = ['revision', 'show-ids']
192
262
    
 
263
    @display_command
193
264
    def run(self, revision=None, show_ids=False):
194
 
        b = find_branch('.')
195
 
        if revision == None:
196
 
            inv = b.read_working_inventory()
 
265
        b = Branch.open_containing('.')[0]
 
266
        if revision is None:
 
267
            inv = b.working_tree().read_working_inventory()
197
268
        else:
198
269
            if len(revision) > 1:
199
270
                raise BzrCommandError('bzr inventory --revision takes'
200
271
                    ' exactly one revision identifier')
201
 
            inv = b.get_revision_inventory(b.lookup_revision(revision[0]))
 
272
            inv = b.get_revision_inventory(revision[0].in_history(b).rev_id)
202
273
 
203
274
        for path, entry in inv.entries():
204
275
            if show_ids:
217
288
    """
218
289
    takes_args = ['source$', 'dest']
219
290
    def run(self, source_list, dest):
220
 
        b = find_branch('.')
 
291
        b, source_list = branch_files(source_list)
221
292
 
222
293
        # TODO: glob expansion on windows?
223
 
        b.move([b.relpath(s) for s in source_list], b.relpath(dest))
 
294
        tree = WorkingTree(b.base, b)
 
295
        b.move(source_list, tree.relpath(dest))
224
296
 
225
297
 
226
298
class cmd_rename(Command):
234
306
 
235
307
    See also the 'move' command, which moves files into a different
236
308
    directory without changing their name.
237
 
 
238
 
    TODO: Some way to rename multiple files without invoking bzr for each
239
 
    one?"""
 
309
    """
 
310
    # TODO: Some way to rename multiple files without invoking 
 
311
    # bzr for each one?"""
240
312
    takes_args = ['from_name', 'to_name']
241
313
    
242
314
    def run(self, from_name, to_name):
243
 
        b = find_branch('.')
244
 
        b.rename_one(b.relpath(from_name), b.relpath(to_name))
245
 
 
 
315
        b, (from_name, to_name) = branch_files((from_name, to_name))
 
316
        b.rename_one(from_name, to_name)
246
317
 
247
318
 
248
319
class cmd_mv(Command):
262
333
    def run(self, names_list):
263
334
        if len(names_list) < 2:
264
335
            raise BzrCommandError("missing file argument")
265
 
        b = find_branch(names_list[0])
266
 
 
267
 
        rel_names = [b.relpath(x) for x in names_list]
 
336
        b, rel_names = branch_files(names_list)
268
337
        
269
338
        if os.path.isdir(names_list[-1]):
270
339
            # move into existing directory
274
343
            if len(names_list) != 2:
275
344
                raise BzrCommandError('to mv multiple files the destination '
276
345
                                      'must be a versioned directory')
277
 
            for pair in b.move(rel_names[0], rel_names[1]):
278
 
                print "%s => %s" % pair
 
346
            b.rename_one(rel_names[0], rel_names[1])
 
347
            print "%s => %s" % (rel_names[0], rel_names[1])
279
348
            
280
349
    
281
 
 
282
 
 
283
350
class cmd_pull(Command):
284
351
    """Pull any changes from another branch into the current one.
285
352
 
286
 
    If the location is omitted, the last-used location will be used.
287
 
    Both the revision history and the working directory will be
288
 
    updated.
 
353
    If there is no default location set, the first pull will set it.  After
 
354
    that, you can omit the location to use the default.  To change the
 
355
    default, use --remember.
289
356
 
290
357
    This command only works on branches that have not diverged.  Branches are
291
358
    considered diverged if both branches have had commits without first
292
359
    pulling from the other.
293
360
 
294
361
    If branches have diverged, you can use 'bzr merge' to pull the text changes
295
 
    from one into the other.
 
362
    from one into the other.  Once one branch has merged, the other should
 
363
    be able to pull it again.
 
364
 
 
365
    If you want to forget your local changes and just update your branch to
 
366
    match the remote one, use --overwrite.
296
367
    """
 
368
    takes_options = ['remember', 'overwrite', 'verbose']
297
369
    takes_args = ['location?']
298
370
 
299
 
    def run(self, location=None):
 
371
    def run(self, location=None, remember=False, overwrite=False, verbose=False):
300
372
        from bzrlib.merge import merge
301
 
        import tempfile
302
373
        from shutil import rmtree
303
374
        import errno
304
 
        from bzrlib.branch import pull_loc
305
375
        
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
 
376
        br_to = Branch.open_containing('.')[0]
 
377
        bound_loc = br_to.get_bound_location()
 
378
        if bound_loc:
 
379
            br = Branch.open(bound_loc)
 
380
            stored_loc = br.get_parent()
 
381
            del br
 
382
        else:
 
383
            stored_loc = br_to.get_parent()
313
384
        if location is None:
314
385
            if stored_loc is None:
315
386
                raise BzrCommandError("No pull location known or specified.")
316
387
            else:
317
 
                print "Using last location: %s" % stored_loc
318
 
                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()
324
 
        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
 
 
341
 
 
 
388
                print "Using saved location: %s" % stored_loc
 
389
                location = stored_loc
 
390
        br_from = Branch.open(location)
 
391
        try:
 
392
            old_rh = br_to.revision_history()
 
393
            br_to.working_tree().pull(br_from, overwrite)
 
394
        except DivergedBranches:
 
395
            raise BzrCommandError("These branches have diverged."
 
396
                                  "  Try merge.")
 
397
        if br_to.get_parent() is None or remember:
 
398
            br_to.set_parent(location)
 
399
 
 
400
        if verbose:
 
401
            new_rh = br_to.revision_history()
 
402
            if old_rh != new_rh:
 
403
                # Something changed
 
404
                from bzrlib.log import show_changed_revisions
 
405
                show_changed_revisions(br_to, old_rh, new_rh)
 
406
 
 
407
 
 
408
class cmd_push(Command):
 
409
    """Push this branch into another branch.
 
410
    
 
411
    The remote branch will not have its working tree populated because this
 
412
    is both expensive, and may not be supported on the remote file system.
 
413
    
 
414
    Some smart servers or protocols *may* put the working tree in place.
 
415
 
 
416
    If there is no default push location set, the first push will set it.
 
417
    After that, you can omit the location to use the default.  To change the
 
418
    default, use --remember.
 
419
 
 
420
    This command only works on branches that have not diverged.  Branches are
 
421
    considered diverged if the branch being pushed to is not an older version
 
422
    of this branch.
 
423
 
 
424
    If branches have diverged, you can use 'bzr push --overwrite' to replace
 
425
    the other branch completely.
 
426
    
 
427
    If you want to ensure you have the different changes in the other branch,
 
428
    do a merge (see bzr help merge) from the other branch, and commit that
 
429
    before doing a 'push --overwrite'.
 
430
    """
 
431
    takes_options = ['remember', 'overwrite', 
 
432
                     Option('create-prefix', 
 
433
                            help='Create the path leading up to the branch '
 
434
                                 'if it does not already exist')]
 
435
    takes_args = ['location?']
 
436
 
 
437
    def run(self, location=None, remember=False, overwrite=False,
 
438
            create_prefix=False, verbose=False):
 
439
        import errno
 
440
        from shutil import rmtree
 
441
        from bzrlib.transport import get_transport
 
442
        
 
443
        br_from = Branch.open_containing('.')[0]
 
444
        stored_loc = br_from.get_push_location()
 
445
        if location is None:
 
446
            if stored_loc is None:
 
447
                raise BzrCommandError("No push location known or specified.")
 
448
            else:
 
449
                print "Using saved location: %s" % stored_loc
 
450
                location = stored_loc
 
451
        try:
 
452
            br_to = Branch.open(location)
 
453
        except NotBranchError:
 
454
            # create a branch.
 
455
            transport = get_transport(location).clone('..')
 
456
            if not create_prefix:
 
457
                try:
 
458
                    transport.mkdir(transport.relpath(location))
 
459
                except NoSuchFile:
 
460
                    raise BzrCommandError("Parent directory of %s "
 
461
                                          "does not exist." % location)
 
462
            else:
 
463
                current = transport.base
 
464
                needed = [(transport, transport.relpath(location))]
 
465
                while needed:
 
466
                    try:
 
467
                        transport, relpath = needed[-1]
 
468
                        transport.mkdir(relpath)
 
469
                        needed.pop()
 
470
                    except NoSuchFile:
 
471
                        new_transport = transport.clone('..')
 
472
                        needed.append((new_transport,
 
473
                                       new_transport.relpath(transport.base)))
 
474
                        if new_transport.base == transport.base:
 
475
                            raise BzrCommandError("Could not creeate "
 
476
                                                  "path prefix.")
 
477
                        
 
478
            NoSuchFile
 
479
            br_to = Branch.initialize(location)
 
480
        try:
 
481
            old_rh = br_to.revision_history()
 
482
            br_to.pull(br_from, overwrite)
 
483
        except DivergedBranches:
 
484
            raise BzrCommandError("These branches have diverged."
 
485
                                  "  Try a merge then push with overwrite.")
 
486
        if br_from.get_push_location() is None or remember:
 
487
            br_from.set_push_location(location)
 
488
 
 
489
        if verbose:
 
490
            new_rh = br_to.revision_history()
 
491
            if old_rh != new_rh:
 
492
                # Something changed
 
493
                from bzrlib.log import show_changed_revisions
 
494
                show_changed_revisions(br_to, old_rh, new_rh)
342
495
 
343
496
class cmd_branch(Command):
344
497
    """Create a new copy of a branch.
348
501
 
349
502
    To retrieve the branch as of a particular revision, supply the --revision
350
503
    parameter, as in "branch foo/bar -r 5".
 
504
 
 
505
    --basis is to speed up branching from remote branches.  When specified, it
 
506
    copies all the file-contents, inventory and revision data from the basis
 
507
    branch before copying anything from the remote branch.
351
508
    """
352
509
    takes_args = ['from_location', 'to_location?']
353
 
    takes_options = ['revision']
 
510
    takes_options = ['revision', 'basis', 'bound', 'unbound']
354
511
    aliases = ['get', 'clone']
355
512
 
356
 
    def run(self, from_location, to_location=None, revision=None):
357
 
        from bzrlib.branch import copy_branch, find_cached_branch
358
 
        import tempfile
 
513
    def run(self, from_location, to_location=None, revision=None, basis=None,
 
514
            bound=False, unbound=False):
 
515
        from bzrlib.clone import copy_branch
359
516
        import errno
360
517
        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
 
518
        if revision is None:
 
519
            revision = [None]
 
520
        elif len(revision) > 1:
 
521
            raise BzrCommandError(
 
522
                'bzr branch --revision takes exactly 1 revision value')
 
523
        if bound and unbound:
 
524
            raise BzrCommandError('Cannot supply both bound and unbound at the same time')
 
525
        try:
 
526
            br_from = Branch.open(from_location)
 
527
        except OSError, e:
 
528
            if e.errno == errno.ENOENT:
 
529
                raise BzrCommandError('Source location "%s" does not'
 
530
                                      ' exist.' % to_location)
 
531
            else:
 
532
                raise
 
533
        br_from.lock_read()
 
534
        try:
 
535
            if basis is not None:
 
536
                basis_branch = Branch.open_containing(basis)[0]
 
537
            else:
 
538
                basis_branch = None
 
539
            if len(revision) == 1 and revision[0] is not None:
 
540
                revision_id = revision[0].in_history(br_from)[1]
 
541
            else:
 
542
                revision_id = None
376
543
            if to_location is None:
377
544
                to_location = os.path.basename(from_location.rstrip("/\\"))
 
545
                name = None
 
546
            else:
 
547
                name = os.path.basename(to_location) + '\n'
378
548
            try:
379
549
                os.mkdir(to_location)
380
550
            except OSError, e:
387
557
                else:
388
558
                    raise
389
559
            try:
390
 
                copy_branch(br_from, to_location, revision[0])
 
560
                copy_branch(br_from, to_location, revision_id, basis_branch)
391
561
            except bzrlib.errors.NoSuchRevision:
392
562
                rmtree(to_location)
393
 
                msg = "The branch %s has no revision %d." % (from_location, revision[0])
394
 
                raise BzrCommandError(msg)
 
563
                msg = "The branch %s has no revision %s." % (from_location, revision[0])
 
564
                raise BzrCommandError(msg)
 
565
            except bzrlib.errors.UnlistableBranch:
 
566
                rmtree(to_location)
 
567
                msg = "The branch %s cannot be used as a --basis" % (basis,)
 
568
                raise BzrCommandError(msg)
 
569
            branch = Branch.open(to_location)
 
570
            if name:
 
571
                name = StringIO(name)
 
572
                branch.put_controlfile('branch-name', name)
395
573
        finally:
396
 
            rmtree(cache_root)
 
574
            br_from.unlock()
 
575
        if bound:
 
576
            branch.bind(br_from)
397
577
 
398
578
 
399
579
class cmd_renames(Command):
400
580
    """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
581
    """
 
582
    # TODO: Option to show renames between two historical versions.
 
583
 
 
584
    # TODO: Only show renames under dir, rather than in the whole branch.
406
585
    takes_args = ['dir?']
407
586
 
 
587
    @display_command
408
588
    def run(self, dir='.'):
409
 
        b = find_branch(dir)
 
589
        b = Branch.open_containing(dir)[0]
410
590
        old_inv = b.basis_tree().inventory
411
 
        new_inv = b.read_working_inventory()
 
591
        new_inv = b.working_tree().read_working_inventory()
412
592
 
413
593
        renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
414
594
        renames.sort()
420
600
    """Show statistical information about a branch."""
421
601
    takes_args = ['branch?']
422
602
    
 
603
    @display_command
423
604
    def run(self, branch=None):
424
605
        import info
425
 
 
426
 
        b = find_branch(branch)
 
606
        b = Branch.open_containing(branch)[0]
427
607
        info.show_info(b)
428
608
 
429
609
 
435
615
    """
436
616
    takes_args = ['file+']
437
617
    takes_options = ['verbose']
 
618
    aliases = ['rm']
438
619
    
439
620
    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)
 
621
        b, file_list = branch_files(file_list)
 
622
        tree = b.working_tree()
 
623
        tree.remove(file_list, verbose=verbose)
442
624
 
443
625
 
444
626
class cmd_file_id(Command):
450
632
    """
451
633
    hidden = True
452
634
    takes_args = ['filename']
 
635
    @display_command
453
636
    def run(self, filename):
454
 
        b = find_branch(filename)
455
 
        i = b.inventory.path2id(b.relpath(filename))
 
637
        b, relpath = Branch.open_containing(filename)
 
638
        i = b.working_tree().inventory.path2id(relpath)
456
639
        if i == None:
457
640
            raise BzrError("%r is not a versioned file" % filename)
458
641
        else:
466
649
    starting at the branch root."""
467
650
    hidden = True
468
651
    takes_args = ['filename']
 
652
    @display_command
469
653
    def run(self, filename):
470
 
        b = find_branch(filename)
 
654
        b, relpath = Branch.open_containing(filename)
471
655
        inv = b.inventory
472
 
        fid = inv.path2id(b.relpath(filename))
 
656
        fid = inv.path2id(relpath)
473
657
        if fid == None:
474
658
            raise BzrError("%r is not a versioned file" % filename)
475
659
        for fip in inv.get_idpath(fid):
479
663
class cmd_revision_history(Command):
480
664
    """Display list of revision ids on this branch."""
481
665
    hidden = True
 
666
    @display_command
482
667
    def run(self):
483
 
        for patchid in find_branch('.').revision_history():
 
668
        for patchid in Branch.open_containing('.')[0].revision_history():
484
669
            print patchid
485
670
 
486
671
 
 
672
class cmd_ancestry(Command):
 
673
    """List all revisions merged into this branch."""
 
674
    hidden = True
 
675
    @display_command
 
676
    def run(self):
 
677
        b = Branch.open_containing('.')[0]
 
678
        for revision_id in b.get_ancestry(b.last_revision()):
 
679
            print revision_id
 
680
 
 
681
 
487
682
class cmd_directories(Command):
488
683
    """Display list of versioned directories in this branch."""
 
684
    @display_command
489
685
    def run(self):
490
 
        for name, ie in find_branch('.').read_working_inventory().directories():
 
686
        for name, ie in (Branch.open_containing('.')[0].working_tree().
 
687
                         read_working_inventory().directories()):
491
688
            if name == '':
492
689
                print '.'
493
690
            else:
503
700
    Recipe for importing a tree of files:
504
701
        cd ~/project
505
702
        bzr init
506
 
        bzr add -v .
 
703
        bzr add .
507
704
        bzr status
508
705
        bzr commit -m 'imported project'
509
706
    """
510
 
    def run(self):
 
707
    takes_args = ['location?']
 
708
    def run(self, location=None):
511
709
        from bzrlib.branch import Branch
512
 
        Branch('.', init=True)
 
710
        if location is None:
 
711
            location = '.'
 
712
        else:
 
713
            # The path has to exist to initialize a
 
714
            # branch inside of it.
 
715
            # Just using os.mkdir, since I don't
 
716
            # believe that we want to create a bunch of
 
717
            # locations if the user supplies an extended path
 
718
            if not os.path.exists(location):
 
719
                os.mkdir(location)
 
720
        Branch.initialize(location)
513
721
 
514
722
 
515
723
class cmd_diff(Command):
518
726
    If files are listed, only the changes in those files are listed.
519
727
    Otherwise, all changes for the tree are listed.
520
728
 
521
 
    TODO: Allow diff across branches.
522
 
 
523
 
    TODO: Option to use external diff command; could be GNU diff, wdiff,
524
 
          or a graphical diff.
525
 
 
526
 
    TODO: Python difflib is not exactly the same as unidiff; should
527
 
          either fix it up or prefer to use an external diff.
528
 
 
529
 
    TODO: If a directory is given, diff everything under that.
530
 
 
531
 
    TODO: Selected-file diff is inefficient and doesn't show you
532
 
          deleted files.
533
 
 
534
 
    TODO: This probably handles non-Unix newlines poorly.
535
 
 
536
729
    examples:
537
730
        bzr diff
538
731
        bzr diff -r1
539
 
        bzr diff -r1:2
 
732
        bzr diff -r1..2
540
733
    """
 
734
    # TODO: Allow diff across branches.
 
735
    # TODO: Option to use external diff command; could be GNU diff, wdiff,
 
736
    #       or a graphical diff.
 
737
 
 
738
    # TODO: Python difflib is not exactly the same as unidiff; should
 
739
    #       either fix it up or prefer to use an external diff.
 
740
 
 
741
    # TODO: If a directory is given, diff everything under that.
 
742
 
 
743
    # TODO: Selected-file diff is inefficient and doesn't show you
 
744
    #       deleted files.
 
745
 
 
746
    # TODO: This probably handles non-Unix newlines poorly.
541
747
    
542
748
    takes_args = ['file*']
543
749
    takes_options = ['revision', 'diff-options']
544
750
    aliases = ['di', 'dif']
545
751
 
 
752
    @display_command
546
753
    def run(self, revision=None, file_list=None, diff_options=None):
547
754
        from bzrlib.diff import show_diff
548
 
 
549
 
        if file_list:
550
 
            b = find_branch(file_list[0])
551
 
            file_list = [b.relpath(f) for f in file_list]
552
 
            if file_list == ['']:
553
 
                # just pointing to top-of-tree
554
 
                file_list = None
555
 
        else:
556
 
            b = find_branch('.')
557
 
 
 
755
        try:
 
756
            b, file_list = inner_branch_files(file_list)
 
757
            b2 = None
 
758
        except NotBranchError:
 
759
            if len(file_list) != 2:
 
760
                raise BzrCommandError("Files are in different branches")
 
761
 
 
762
            b, file1 = Branch.open_containing(file_list[0])
 
763
            b2, file2 = Branch.open_containing(file_list[1])
 
764
            if file1 != "" or file2 != "":
 
765
                raise BzrCommandError("Files are in different branches")
 
766
            file_list = None
558
767
        if revision is not None:
 
768
            if b2 is not None:
 
769
                raise BzrCommandError("Can't specify -r with two branches")
559
770
            if len(revision) == 1:
560
 
                show_diff(b, revision[0], specific_files=file_list,
561
 
                          external_diff_options=diff_options)
 
771
                return show_diff(b, revision[0], specific_files=file_list,
 
772
                                 external_diff_options=diff_options)
562
773
            elif len(revision) == 2:
563
 
                show_diff(b, revision[0], specific_files=file_list,
564
 
                          external_diff_options=diff_options,
565
 
                          revision2=revision[1])
 
774
                return show_diff(b, revision[0], specific_files=file_list,
 
775
                                 external_diff_options=diff_options,
 
776
                                 revision2=revision[1])
566
777
            else:
567
778
                raise BzrCommandError('bzr diff --revision takes exactly one or two revision identifiers')
568
779
        else:
569
 
            show_diff(b, None, specific_files=file_list,
570
 
                      external_diff_options=diff_options)
571
 
 
572
 
        
 
780
            return show_diff(b, None, specific_files=file_list,
 
781
                             external_diff_options=diff_options, b2=b2)
573
782
 
574
783
 
575
784
class cmd_deleted(Command):
576
785
    """List files deleted in the working tree.
577
 
 
578
 
    TODO: Show files deleted since a previous revision, or between two revisions.
579
786
    """
 
787
    # TODO: Show files deleted since a previous revision, or
 
788
    # between two revisions.
 
789
    # TODO: Much more efficient way to do this: read in new
 
790
    # directories with readdir, rather than stating each one.  Same
 
791
    # level of effort but possibly much less IO.  (Or possibly not,
 
792
    # if the directories are very large...)
 
793
    @display_command
580
794
    def run(self, show_ids=False):
581
 
        b = find_branch('.')
 
795
        b = Branch.open_containing('.')[0]
582
796
        old = b.basis_tree()
583
797
        new = b.working_tree()
584
 
 
585
 
        ## TODO: Much more efficient way to do this: read in new
586
 
        ## directories with readdir, rather than stating each one.  Same
587
 
        ## level of effort but possibly much less IO.  (Or possibly not,
588
 
        ## if the directories are very large...)
589
 
 
590
798
        for path, ie in old.inventory.iter_entries():
591
799
            if not new.has_id(ie.file_id):
592
800
                if show_ids:
598
806
class cmd_modified(Command):
599
807
    """List files modified in working tree."""
600
808
    hidden = True
 
809
    @display_command
601
810
    def run(self):
602
811
        from bzrlib.delta import compare_trees
603
812
 
604
 
        b = find_branch('.')
 
813
        b = Branch.open_containing('.')[0]
605
814
        td = compare_trees(b.basis_tree(), b.working_tree())
606
815
 
607
 
        for path, id, kind in td.modified:
 
816
        for path, id, kind, text_modified, meta_modified in td.modified:
608
817
            print path
609
818
 
610
819
 
612
821
class cmd_added(Command):
613
822
    """List files added in working tree."""
614
823
    hidden = True
 
824
    @display_command
615
825
    def run(self):
616
 
        b = find_branch('.')
 
826
        b = Branch.open_containing('.')[0]
617
827
        wt = b.working_tree()
618
828
        basis_inv = b.basis_tree().inventory
619
829
        inv = wt.inventory
633
843
    The root is the nearest enclosing directory with a .bzr control
634
844
    directory."""
635
845
    takes_args = ['filename?']
 
846
    @display_command
636
847
    def run(self, filename=None):
637
848
        """Print the branch root."""
638
 
        b = find_branch(filename)
639
 
        print getattr(b, 'base', None) or getattr(b, 'baseurl')
 
849
        b = Branch.open_containing(filename)[0]
 
850
        print b.base
640
851
 
641
852
 
642
853
class cmd_log(Command):
643
854
    """Show log of this branch.
644
855
 
645
 
    To request a range of logs, you can use the command -r begin:end
646
 
    -r revision requests a specific revision, -r :end or -r begin: are
 
856
    To request a range of logs, you can use the command -r begin..end
 
857
    -r revision requests a specific revision, -r ..end or -r begin.. are
647
858
    also valid.
648
 
 
649
 
    --message allows you to give a regular expression, which will be evaluated
650
 
    so that only matching entries will be displayed.
651
 
 
652
 
    TODO: Make --revision support uuid: and hash: [future tag:] notation.
653
 
  
654
859
    """
655
860
 
 
861
    # TODO: Make --revision support uuid: and hash: [future tag:] notation.
 
862
 
656
863
    takes_args = ['filename?']
657
 
    takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision',
658
 
                     'long', 'message', 'short',]
659
 
    
 
864
    takes_options = [Option('forward', 
 
865
                            help='show from oldest to newest'),
 
866
                     'timezone', 'verbose', 
 
867
                     'show-ids', 'revision',
 
868
                     Option('line', help='format with one line per revision'),
 
869
                     'long', 
 
870
                     Option('message',
 
871
                            help='show revisions whose message matches this regexp',
 
872
                            type=str),
 
873
                     Option('short', help='use moderately short format'),
 
874
                     ]
 
875
    @display_command
660
876
    def run(self, filename=None, timezone='original',
661
877
            verbose=False,
662
878
            show_ids=False,
664
880
            revision=None,
665
881
            message=None,
666
882
            long=False,
667
 
            short=False):
 
883
            short=False,
 
884
            line=False):
668
885
        from bzrlib.log import log_formatter, show_log
669
886
        import codecs
670
 
 
 
887
        assert message is None or isinstance(message, basestring), \
 
888
            "invalid message argument %r" % message
671
889
        direction = (forward and 'forward') or 'reverse'
672
890
        
673
891
        if filename:
674
 
            b = find_branch(filename)
675
 
            fp = b.relpath(filename)
676
 
            if fp:
677
 
                file_id = b.read_working_inventory().path2id(fp)
 
892
            b, fp = Branch.open_containing(filename)
 
893
            if fp != '':
 
894
                try:
 
895
                    inv = b.working_tree().read_working_inventory()
 
896
                except NoWorkingTree:
 
897
                    inv = b.get_inventory(b.last_revision())
 
898
                file_id = inv.path2id(fp)
678
899
            else:
679
900
                file_id = None  # points to branch root
680
901
        else:
681
 
            b = find_branch('.')
 
902
            b, relpath = Branch.open_containing('.')
682
903
            file_id = None
683
904
 
684
905
        if revision is None:
685
906
            rev1 = None
686
907
            rev2 = None
687
908
        elif len(revision) == 1:
688
 
            rev1 = rev2 = b.get_revision_info(revision[0])[0]
 
909
            rev1 = rev2 = revision[0].in_history(b).revno
689
910
        elif len(revision) == 2:
690
 
            rev1 = b.get_revision_info(revision[0])[0]
691
 
            rev2 = b.get_revision_info(revision[1])[0]
 
911
            rev1 = revision[0].in_history(b).revno
 
912
            rev2 = revision[1].in_history(b).revno
692
913
        else:
693
914
            raise BzrCommandError('bzr log --revision takes one or two values.')
694
915
 
697
918
        if rev2 == 0:
698
919
            rev2 = None
699
920
 
700
 
        mutter('encoding log as %r' % bzrlib.user_encoding)
 
921
        mutter('encoding log as %r', bzrlib.user_encoding)
701
922
 
702
923
        # use 'replace' so that we don't abort if trying to write out
703
924
        # in e.g. the default C locale.
704
925
        outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
705
926
 
706
 
        if not short:
707
 
            log_format = 'long'
708
 
        else:
 
927
        log_format = 'long'
 
928
        if short:
709
929
            log_format = 'short'
 
930
        if line:
 
931
            log_format = 'line'
710
932
        lf = log_formatter(log_format,
711
933
                           show_ids=show_ids,
712
934
                           to_file=outf,
729
951
    A more user-friendly interface is "bzr log FILE"."""
730
952
    hidden = True
731
953
    takes_args = ["filename"]
 
954
    @display_command
732
955
    def run(self, filename):
733
 
        b = find_branch(filename)
734
 
        inv = b.read_working_inventory()
735
 
        file_id = inv.path2id(b.relpath(filename))
 
956
        b, relpath = Branch.open_containing(filename)[0]
 
957
        inv = b.working_tree().read_working_inventory()
 
958
        file_id = inv.path2id(relpath)
736
959
        for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
737
960
            print "%6d %s" % (revno, what)
738
961
 
739
962
 
740
963
class cmd_ls(Command):
741
964
    """List files in a tree.
742
 
 
743
 
    TODO: Take a revision or remote path and list that tree instead.
744
965
    """
 
966
    # TODO: Take a revision or remote path and list that tree instead.
745
967
    hidden = True
746
 
    def run(self, revision=None, verbose=False):
747
 
        b = find_branch('.')
 
968
    takes_options = ['verbose', 'revision',
 
969
                     Option('non-recursive',
 
970
                            help='don\'t recurse into sub-directories'),
 
971
                     Option('from-root',
 
972
                            help='Print all paths from the root of the branch.'),
 
973
                     Option('unknown', help='Print unknown files'),
 
974
                     Option('versioned', help='Print versioned files'),
 
975
                     Option('ignored', help='Print ignored files'),
 
976
 
 
977
                     Option('null', help='Null separate the files'),
 
978
                    ]
 
979
    @display_command
 
980
    def run(self, revision=None, verbose=False, 
 
981
            non_recursive=False, from_root=False,
 
982
            unknown=False, versioned=False, ignored=False,
 
983
            null=False):
 
984
 
 
985
        if verbose and null:
 
986
            raise BzrCommandError('Cannot set both --verbose and --null')
 
987
        all = not (unknown or versioned or ignored)
 
988
 
 
989
        selection = {'I':ignored, '?':unknown, 'V':versioned}
 
990
 
 
991
        b, relpath = Branch.open_containing('.')
 
992
        if from_root:
 
993
            relpath = ''
 
994
        elif relpath:
 
995
            relpath += '/'
748
996
        if revision == None:
749
997
            tree = b.working_tree()
750
998
        else:
751
 
            tree = b.revision_tree(b.lookup_revision(revision))
752
 
 
753
 
        for fp, fc, kind, fid in tree.list_files():
754
 
            if verbose:
755
 
                if kind == 'directory':
756
 
                    kindch = '/'
757
 
                elif kind == 'file':
758
 
                    kindch = ''
 
999
            tree = b.revision_tree(revision[0].in_history(b).rev_id)
 
1000
        for fp, fc, kind, fid, entry in tree.list_files():
 
1001
            if fp.startswith(relpath):
 
1002
                fp = fp[len(relpath):]
 
1003
                if non_recursive and '/' in fp:
 
1004
                    continue
 
1005
                if not all and not selection[fc]:
 
1006
                    continue
 
1007
                if verbose:
 
1008
                    kindch = entry.kind_character()
 
1009
                    print '%-8s %s%s' % (fc, fp, kindch)
 
1010
                elif null:
 
1011
                    sys.stdout.write(fp)
 
1012
                    sys.stdout.write('\0')
 
1013
                    sys.stdout.flush()
759
1014
                else:
760
 
                    kindch = '???'
761
 
 
762
 
                print '%-8s %s%s' % (fc, fp, kindch)
763
 
            else:
764
 
                print fp
 
1015
                    print fp
765
1016
 
766
1017
 
767
1018
 
768
1019
class cmd_unknowns(Command):
769
1020
    """List unknown files."""
 
1021
    @display_command
770
1022
    def run(self):
771
1023
        from bzrlib.osutils import quotefn
772
 
        for f in find_branch('.').unknowns():
 
1024
        for f in Branch.open_containing('.')[0].unknowns():
773
1025
            print quotefn(f)
774
1026
 
775
1027
 
780
1032
    To remove patterns from the ignore list, edit the .bzrignore file.
781
1033
 
782
1034
    If the pattern contains a slash, it is compared to the whole path
783
 
    from the branch root.  Otherwise, it is comapred to only the last
784
 
    component of the path.
 
1035
    from the branch root.  Otherwise, it is compared to only the last
 
1036
    component of the path.  To match a file only in the root directory,
 
1037
    prepend './'.
785
1038
 
786
1039
    Ignore patterns are case-insensitive on case-insensitive systems.
787
1040
 
791
1044
        bzr ignore ./Makefile
792
1045
        bzr ignore '*.class'
793
1046
    """
 
1047
    # TODO: Complain if the filename is absolute
794
1048
    takes_args = ['name_pattern']
795
1049
    
796
1050
    def run(self, name_pattern):
797
1051
        from bzrlib.atomicfile import AtomicFile
798
1052
        import os.path
799
1053
 
800
 
        b = find_branch('.')
 
1054
        b, relpath = Branch.open_containing('.')
801
1055
        ifn = b.abspath('.bzrignore')
802
1056
 
803
1057
        if os.path.exists(ifn):
836
1090
    """List ignored files and the patterns that matched them.
837
1091
 
838
1092
    See also: bzr ignore"""
 
1093
    @display_command
839
1094
    def run(self):
840
 
        tree = find_branch('.').working_tree()
841
 
        for path, file_class, kind, file_id in tree.list_files():
 
1095
        tree = Branch.open_containing('.')[0].working_tree()
 
1096
        for path, file_class, kind, file_id, entry in tree.list_files():
842
1097
            if file_class != 'I':
843
1098
                continue
844
1099
            ## XXX: Slightly inefficient since this was already calculated
855
1110
    hidden = True
856
1111
    takes_args = ['revno']
857
1112
    
 
1113
    @display_command
858
1114
    def run(self, revno):
859
1115
        try:
860
1116
            revno = int(revno)
861
1117
        except ValueError:
862
1118
            raise BzrCommandError("not a valid revision-number: %r" % revno)
863
1119
 
864
 
        print find_branch('.').lookup_revision(revno)
 
1120
        print Branch.open_containing('.')[0].get_rev_id(revno)
865
1121
 
866
1122
 
867
1123
class cmd_export(Command):
874
1130
    is found exports to a directory (equivalent to --format=dir).
875
1131
 
876
1132
    Root may be the top directory for tar, tgz and tbz2 formats. If none
877
 
    is given, the top directory will be the root name of the file."""
878
 
    # TODO: list known exporters
 
1133
    is given, the top directory will be the root name of the file.
 
1134
 
 
1135
    Note: export of tree with non-ascii filenames to zip is not supported.
 
1136
 
 
1137
    Supported formats       Autodetected by extension
 
1138
    -----------------       -------------------------
 
1139
         dir                            -
 
1140
         tar                          .tar
 
1141
         tbz2                    .tar.bz2, .tbz2
 
1142
         tgz                      .tar.gz, .tgz
 
1143
         zip                          .zip
 
1144
    """
879
1145
    takes_args = ['dest']
880
1146
    takes_options = ['revision', 'format', 'root']
881
1147
    def run(self, dest, revision=None, format=None, root=None):
882
1148
        import os.path
883
 
        b = find_branch('.')
 
1149
        from bzrlib.export import export
 
1150
        b = Branch.open_containing('.')[0]
884
1151
        if revision is None:
885
 
            rev_id = b.last_patch()
 
1152
            rev_id = b.last_revision()
886
1153
        else:
887
1154
            if len(revision) != 1:
888
1155
                raise BzrError('bzr export --revision takes exactly 1 argument')
889
 
            revno, rev_id = b.get_revision_info(revision[0])
 
1156
            rev_id = revision[0].in_history(b).rev_id
890
1157
        t = b.revision_tree(rev_id)
891
 
        root, ext = os.path.splitext(dest)
892
 
        if not format:
893
 
            if ext in (".tar",):
894
 
                format = "tar"
895
 
            elif ext in (".gz", ".tgz"):
896
 
                format = "tgz"
897
 
            elif ext in (".bz2", ".tbz2"):
898
 
                format = "tbz2"
899
 
            else:
900
 
                format = "dir"
901
 
        t.export(dest, format, root)
 
1158
        try:
 
1159
            export(t, dest, format, root)
 
1160
        except errors.NoSuchExportFormat, e:
 
1161
            raise BzrCommandError('Unsupported export format: %s' % e.format)
902
1162
 
903
1163
 
904
1164
class cmd_cat(Command):
907
1167
    takes_options = ['revision']
908
1168
    takes_args = ['filename']
909
1169
 
 
1170
    @display_command
910
1171
    def run(self, filename, revision=None):
911
 
        if revision == None:
 
1172
        if revision is None:
912
1173
            raise BzrCommandError("bzr cat requires a revision number")
913
1174
        elif len(revision) != 1:
914
1175
            raise BzrCommandError("bzr cat --revision takes exactly one number")
915
 
        b = find_branch('.')
916
 
        b.print_file(b.relpath(filename), revision[0])
 
1176
        b, relpath = Branch.open_containing(filename)
 
1177
        b.print_file(relpath, revision[0].in_history(b).revno)
917
1178
 
918
1179
 
919
1180
class cmd_local_time_offset(Command):
920
1181
    """Show the offset in seconds from GMT to local time."""
921
1182
    hidden = True    
 
1183
    @display_command
922
1184
    def run(self):
923
1185
        print bzrlib.osutils.local_time_offset()
924
1186
 
936
1198
    A selected-file commit may fail in some cases where the committed
937
1199
    tree would be invalid, such as trying to commit a file in a
938
1200
    newly-added directory that is not itself committed.
939
 
 
940
 
    TODO: Run hooks on tree to-be-committed, and after commit.
941
 
 
942
 
    TODO: Strict commit that fails if there are unknown or deleted files.
943
1201
    """
 
1202
    # TODO: Run hooks on tree to-be-committed, and after commit.
 
1203
 
 
1204
    # TODO: Strict commit that fails if there are deleted files.
 
1205
    #       (what does "deleted files" mean ??)
 
1206
 
 
1207
    # TODO: Give better message for -s, --summary, used by tla people
 
1208
 
 
1209
    # XXX: verbose currently does nothing
 
1210
 
944
1211
    takes_args = ['selected*']
945
 
    takes_options = ['message', 'file', 'verbose', 'unchanged']
 
1212
    takes_options = ['message', 'verbose', 
 
1213
                     Option('unchanged',
 
1214
                            help='commit even if nothing has changed'),
 
1215
                     Option('file', type=str, 
 
1216
                            argname='msgfile',
 
1217
                            help='file containing commit message'),
 
1218
                     Option('strict',
 
1219
                            help="refuse to commit if there are unknown "
 
1220
                            "files in the working tree."),
 
1221
                     ]
946
1222
    aliases = ['ci', 'checkin']
947
1223
 
948
 
    # TODO: Give better message for -s, --summary, used by tla people
949
 
    
950
1224
    def run(self, message=None, file=None, verbose=True, selected_list=None,
951
 
            unchanged=False):
952
 
        from bzrlib.errors import PointlessCommit
 
1225
            unchanged=False, strict=False):
 
1226
        from bzrlib.errors import (PointlessCommit, ConflictsInTree,
 
1227
                StrictCommitFailed)
953
1228
        from bzrlib.msgeditor import edit_commit_message
954
1229
        from bzrlib.status import show_status
955
1230
        from cStringIO import StringIO
956
1231
 
957
 
        b = find_branch('.')
958
 
        if selected_list:
959
 
            selected_list = [b.relpath(s) for s in selected_list]
960
 
            
961
 
        if not message and not file:
 
1232
        b, selected_list = branch_files(selected_list)
 
1233
        if message is None and not file:
962
1234
            catcher = StringIO()
963
1235
            show_status(b, specific_files=selected_list,
964
1236
                        to_file=catcher)
965
1237
            message = edit_commit_message(catcher.getvalue())
966
 
            
 
1238
 
967
1239
            if message is None:
968
1240
                raise BzrCommandError("please specify a commit message"
969
1241
                                      " with either --message or --file")
974
1246
            import codecs
975
1247
            message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
976
1248
 
 
1249
        if message == "":
 
1250
                raise BzrCommandError("empty commit message specified")
 
1251
            
977
1252
        try:
978
 
            b.commit(message, verbose=verbose,
979
 
                     specific_files=selected_list,
980
 
                     allow_pointless=unchanged)
 
1253
            b.working_tree().commit(message, specific_files=selected_list,
 
1254
                     allow_pointless=unchanged, strict=strict)
981
1255
        except PointlessCommit:
982
1256
            # FIXME: This should really happen before the file is read in;
983
1257
            # perhaps prepare the commit; get the message; then actually commit
984
1258
            raise BzrCommandError("no changes to commit",
985
1259
                                  ["use --unchanged to commit anyhow"])
 
1260
        except ConflictsInTree:
 
1261
            raise BzrCommandError("Conflicts detected in working tree.  "
 
1262
                'Use "bzr conflicts" to list, "bzr resolve FILE" to resolve.')
 
1263
        except StrictCommitFailed:
 
1264
            raise BzrCommandError("Commit refused because there are unknown "
 
1265
                                  "files in the working tree.")
 
1266
        except errors.CannotInstallRevisions, e:
 
1267
            raise BzrCommandError(e.msg)
986
1268
 
987
1269
 
988
1270
class cmd_check(Command):
990
1272
 
991
1273
    This command checks various invariants about the branch storage to
992
1274
    detect data corruption or bzr bugs.
993
 
 
994
 
    If given the --update flag, it will update some optional fields
995
 
    to help ensure data consistency.
996
1275
    """
997
1276
    takes_args = ['dir?']
 
1277
    takes_options = ['verbose']
998
1278
 
999
 
    def run(self, dir='.'):
 
1279
    def run(self, dir='.', verbose=False):
1000
1280
        from bzrlib.check import check
1001
 
 
1002
 
        check(find_branch(dir))
 
1281
        check(Branch.open_containing(dir)[0], verbose)
1003
1282
 
1004
1283
 
1005
1284
class cmd_scan_cache(Command):
1027
1306
 
1028
1307
    The check command or bzr developers may sometimes advise you to run
1029
1308
    this command.
 
1309
 
 
1310
    This version of this command upgrades from the full-text storage
 
1311
    used by bzr 0.0.8 and earlier to the weave format (v5).
1030
1312
    """
1031
1313
    takes_args = ['dir?']
1032
1314
 
1033
1315
    def run(self, dir='.'):
1034
1316
        from bzrlib.upgrade import upgrade
1035
 
        upgrade(find_branch(dir))
1036
 
 
 
1317
        upgrade(dir)
1037
1318
 
1038
1319
 
1039
1320
class cmd_whoami(Command):
1040
1321
    """Show bzr user id."""
1041
1322
    takes_options = ['email']
1042
1323
    
 
1324
    @display_command
1043
1325
    def run(self, email=False):
1044
1326
        try:
1045
 
            b = bzrlib.branch.find_branch('.')
1046
 
        except:
1047
 
            b = None
 
1327
            b = bzrlib.branch.Branch.open_containing('.')[0]
 
1328
            config = bzrlib.config.BranchConfig(b)
 
1329
        except NotBranchError:
 
1330
            config = bzrlib.config.GlobalConfig()
1048
1331
        
1049
1332
        if email:
1050
 
            print bzrlib.osutils.user_email(b)
1051
 
        else:
1052
 
            print bzrlib.osutils.username(b)
1053
 
 
 
1333
            print config.user_email()
 
1334
        else:
 
1335
            print config.username()
 
1336
 
 
1337
class cmd_nick(Command):
 
1338
    """\
 
1339
    Print or set the branch nickname.  
 
1340
    If unset, the tree root directory name is used as the nickname
 
1341
    To print the current nickname, execute with no argument.  
 
1342
    """
 
1343
    takes_args = ['nickname?']
 
1344
    def run(self, nickname=None):
 
1345
        branch = Branch.open_containing('.')[0]
 
1346
        if nickname is None:
 
1347
            self.printme(branch)
 
1348
        else:
 
1349
            branch.nick = nickname
 
1350
 
 
1351
    @display_command
 
1352
    def printme(self, branch):
 
1353
        print branch.nick 
1054
1354
 
1055
1355
class cmd_selftest(Command):
1056
 
    """Run internal test suite"""
 
1356
    """Run internal test suite.
 
1357
    
 
1358
    This creates temporary test directories in the working directory,
 
1359
    but not existing data is affected.  These directories are deleted
 
1360
    if the tests pass, or left behind to help in debugging if they
 
1361
    fail and --keep-output is specified.
 
1362
    
 
1363
    If arguments are given, they are regular expressions that say
 
1364
    which tests should run.
 
1365
    """
 
1366
    # TODO: --list should give a list of all available tests
1057
1367
    hidden = True
1058
 
    takes_options = ['verbose', 'pattern']
1059
 
    def run(self, verbose=False, pattern=".*"):
 
1368
    takes_args = ['testspecs*']
 
1369
    takes_options = ['verbose', 
 
1370
                     Option('one', help='stop when one test fails'),
 
1371
                     Option('keep-output', 
 
1372
                            help='keep output directories when tests fail')
 
1373
                    ]
 
1374
 
 
1375
    def run(self, testspecs_list=None, verbose=False, one=False,
 
1376
            keep_output=False):
1060
1377
        import bzrlib.ui
1061
1378
        from bzrlib.selftest import selftest
1062
1379
        # we don't want progress meters from the tests to go to the
1066
1383
        bzrlib.trace.info('running tests...')
1067
1384
        try:
1068
1385
            bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1069
 
            result = selftest(verbose=verbose, pattern=pattern)
 
1386
            if testspecs_list is not None:
 
1387
                pattern = '|'.join(testspecs_list)
 
1388
            else:
 
1389
                pattern = ".*"
 
1390
            result = selftest(verbose=verbose, 
 
1391
                              pattern=pattern,
 
1392
                              stop_on_failure=one, 
 
1393
                              keep_output=keep_output)
1070
1394
            if result:
1071
1395
                bzrlib.trace.info('tests passed')
1072
1396
            else:
1092
1416
 
1093
1417
class cmd_version(Command):
1094
1418
    """Show version of bzr."""
 
1419
    @display_command
1095
1420
    def run(self):
1096
1421
        show_version()
1097
1422
 
1098
1423
class cmd_rocks(Command):
1099
1424
    """Statement of optimism."""
1100
1425
    hidden = True
 
1426
    @display_command
1101
1427
    def run(self):
1102
1428
        print "it sure does!"
1103
1429
 
1104
1430
 
1105
1431
class cmd_find_merge_base(Command):
1106
1432
    """Find and print a base revision for merging two branches.
1107
 
 
1108
 
    TODO: Options to specify revisions on either side, as if
1109
 
          merging only part of the history.
1110
1433
    """
 
1434
    # TODO: Options to specify revisions on either side, as if
 
1435
    #       merging only part of the history.
1111
1436
    takes_args = ['branch', 'other']
1112
1437
    hidden = True
1113
1438
    
 
1439
    @display_command
1114
1440
    def run(self, branch, other):
1115
1441
        from bzrlib.revision import common_ancestor, MultipleRevisionSources
1116
1442
        
1117
 
        branch1 = find_branch(branch)
1118
 
        branch2 = find_branch(other)
 
1443
        branch1 = Branch.open_containing(branch)[0]
 
1444
        branch2 = Branch.open_containing(other)[0]
1119
1445
 
1120
1446
        history_1 = branch1.revision_history()
1121
1447
        history_2 = branch2.revision_history()
1122
1448
 
1123
 
        last1 = branch1.last_patch()
1124
 
        last2 = branch2.last_patch()
 
1449
        last1 = branch1.last_revision()
 
1450
        last2 = branch2.last_revision()
1125
1451
 
1126
1452
        source = MultipleRevisionSources(branch1, branch2)
1127
1453
        
1170
1496
    --force is given.
1171
1497
    """
1172
1498
    takes_args = ['branch?']
1173
 
    takes_options = ['revision', 'force', 'merge-type']
 
1499
    takes_options = ['revision', 'force', 'merge-type', 'reprocess',
 
1500
                     Option('show-base', help="Show base revision text in "
 
1501
                            "conflicts")]
1174
1502
 
1175
 
    def run(self, branch='.', revision=None, force=False, 
1176
 
            merge_type=None):
 
1503
    def run(self, branch=None, revision=None, force=False, merge_type=None,
 
1504
            show_base=False, reprocess=False):
1177
1505
        from bzrlib.merge import merge
1178
1506
        from bzrlib.merge_core import ApplyMerge3
1179
1507
        if merge_type is None:
1180
1508
            merge_type = ApplyMerge3
1181
 
 
 
1509
        if branch is None:
 
1510
            branch = Branch.open_containing('.')[0].get_parent()
 
1511
            if branch is None:
 
1512
                raise BzrCommandError("No merge location known or specified.")
 
1513
            else:
 
1514
                print "Using saved location: %s" % branch 
1182
1515
        if revision is None or len(revision) < 1:
1183
1516
            base = [None, None]
1184
1517
            other = [branch, -1]
1185
1518
        else:
1186
1519
            if len(revision) == 1:
1187
 
                other = [branch, revision[0]]
1188
1520
                base = [None, None]
 
1521
                other_branch = Branch.open_containing(branch)[0]
 
1522
                revno = revision[0].in_history(other_branch).revno
 
1523
                other = [branch, revno]
1189
1524
            else:
1190
1525
                assert len(revision) == 2
1191
1526
                if None in revision:
1192
1527
                    raise BzrCommandError(
1193
1528
                        "Merge doesn't permit that revision specifier.")
1194
 
                base = [branch, revision[0]]
1195
 
                other = [branch, revision[1]]
 
1529
                b = Branch.open_containing(branch)[0]
 
1530
 
 
1531
                base = [branch, revision[0].in_history(b).revno]
 
1532
                other = [branch, revision[1].in_history(b).revno]
1196
1533
 
1197
1534
        try:
1198
 
            merge(other, base, check_clean=(not force), merge_type=merge_type)
 
1535
            conflict_count = merge(other, base, check_clean=(not force),
 
1536
                                   merge_type=merge_type, reprocess=reprocess,
 
1537
                                   show_base=show_base)
 
1538
            if conflict_count != 0:
 
1539
                return 1
 
1540
            else:
 
1541
                return 0
1199
1542
        except bzrlib.errors.AmbiguousBase, e:
1200
1543
            m = ("sorry, bzr can't determine the right merge base yet\n"
1201
1544
                 "candidates are:\n  "
1206
1549
            log_error(m)
1207
1550
 
1208
1551
 
 
1552
class cmd_remerge(Command):
 
1553
    """Redo a merge.
 
1554
    """
 
1555
    takes_args = ['file*']
 
1556
    takes_options = ['merge-type', 'reprocess',
 
1557
                     Option('show-base', help="Show base revision text in "
 
1558
                            "conflicts")]
 
1559
 
 
1560
    def run(self, file_list=None, merge_type=None, show_base=False,
 
1561
            reprocess=False):
 
1562
        from bzrlib.merge import merge_inner, transform_tree
 
1563
        from bzrlib.merge_core import ApplyMerge3
 
1564
        if merge_type is None:
 
1565
            merge_type = ApplyMerge3
 
1566
        b, file_list = branch_files(file_list)
 
1567
        b.lock_write()
 
1568
        try:
 
1569
            pending_merges = b.working_tree().pending_merges() 
 
1570
            if len(pending_merges) != 1:
 
1571
                raise BzrCommandError("Sorry, remerge only works after normal"
 
1572
                                      + " merges.  Not cherrypicking or"
 
1573
                                      + "multi-merges.")
 
1574
            this_tree = b.working_tree()
 
1575
            base_revision = common_ancestor(b.last_revision(), 
 
1576
                                            pending_merges[0], b)
 
1577
            base_tree = b.revision_tree(base_revision)
 
1578
            other_tree = b.revision_tree(pending_merges[0])
 
1579
            interesting_ids = None
 
1580
            if file_list is not None:
 
1581
                interesting_ids = set()
 
1582
                for filename in file_list:
 
1583
                    file_id = this_tree.path2id(filename)
 
1584
                    interesting_ids.add(file_id)
 
1585
                    if this_tree.kind(file_id) != "directory":
 
1586
                        continue
 
1587
                    
 
1588
                    for name, ie in this_tree.inventory.iter_entries(file_id):
 
1589
                        interesting_ids.add(ie.file_id)
 
1590
            transform_tree(this_tree, b.basis_tree(), interesting_ids)
 
1591
            if file_list is None:
 
1592
                restore_files = list(this_tree.iter_conflicts())
 
1593
            else:
 
1594
                restore_files = file_list
 
1595
            for filename in restore_files:
 
1596
                try:
 
1597
                    restore(this_tree.abspath(filename))
 
1598
                except NotConflicted:
 
1599
                    pass
 
1600
            conflicts =  merge_inner(b, other_tree, base_tree, 
 
1601
                                     interesting_ids = interesting_ids, 
 
1602
                                     other_rev_id=pending_merges[0], 
 
1603
                                     merge_type=merge_type, 
 
1604
                                     show_base=show_base,
 
1605
                                     reprocess=reprocess)
 
1606
        finally:
 
1607
            b.unlock()
 
1608
        if conflicts > 0:
 
1609
            return 1
 
1610
        else:
 
1611
            return 0
 
1612
 
1209
1613
class cmd_revert(Command):
1210
1614
    """Reverse all changes since the last commit.
1211
1615
 
1218
1622
    aliases = ['merge-revert']
1219
1623
 
1220
1624
    def run(self, revision=None, no_backup=False, file_list=None):
1221
 
        from bzrlib.merge import merge
1222
 
        from bzrlib.branch import Branch
 
1625
        from bzrlib.merge import merge_inner
1223
1626
        from bzrlib.commands import parse_spec
1224
 
 
1225
1627
        if file_list is not None:
1226
1628
            if len(file_list) == 0:
1227
1629
                raise BzrCommandError("No files specified")
 
1630
        else:
 
1631
            file_list = []
1228
1632
        if revision is None:
1229
 
            revision = [-1]
 
1633
            revno = -1
 
1634
            b = Branch.open_containing('.')[0]
 
1635
            rev_id = b.last_revision()
1230
1636
        elif len(revision) != 1:
1231
1637
            raise BzrCommandError('bzr revert --revision takes exactly 1 argument')
1232
 
        merge(('.', revision[0]), parse_spec('.'),
1233
 
              check_clean=False,
1234
 
              ignore_zero=True,
1235
 
              backup_files=not no_backup,
1236
 
              file_list=file_list)
1237
 
        if not file_list:
1238
 
            Branch('.').set_pending_merges([])
 
1638
        else:
 
1639
            b, file_list = branch_files(file_list)
 
1640
            rev_id = revision[0].in_history(b).rev_id
 
1641
        b.working_tree().revert(file_list, b.revision_tree(rev_id),
 
1642
                                not no_backup)
1239
1643
 
1240
1644
 
1241
1645
class cmd_assert_fail(Command):
1253
1657
    takes_args = ['topic?']
1254
1658
    aliases = ['?']
1255
1659
    
 
1660
    @display_command
1256
1661
    def run(self, topic=None, long=False):
1257
1662
        import help
1258
1663
        if topic is None and long:
1268
1673
    aliases = ['s-c']
1269
1674
    hidden = True
1270
1675
    
 
1676
    @display_command
1271
1677
    def run(self, context=None):
1272
1678
        import shellcomplete
1273
1679
        shellcomplete.shellcomplete(context)
1274
1680
 
1275
1681
 
 
1682
class cmd_fetch(Command):
 
1683
    """Copy in history from another branch but don't merge it.
 
1684
 
 
1685
    This is an internal method used for pull and merge."""
 
1686
    hidden = True
 
1687
    takes_args = ['from_branch', 'to_branch']
 
1688
    def run(self, from_branch, to_branch):
 
1689
        from bzrlib.fetch import Fetcher
 
1690
        from bzrlib.branch import Branch
 
1691
        from_b = Branch.open(from_branch)
 
1692
        to_b = Branch.open(to_branch)
 
1693
        from_b.lock_read()
 
1694
        try:
 
1695
            to_b.lock_write()
 
1696
            try:
 
1697
                Fetcher(to_b, from_b)
 
1698
            finally:
 
1699
                to_b.unlock()
 
1700
        finally:
 
1701
            from_b.unlock()
 
1702
 
 
1703
 
1276
1704
class cmd_missing(Command):
1277
1705
    """What is missing in this branch relative to other branch.
1278
1706
    """
 
1707
    # TODO: rewrite this in terms of ancestry so that it shows only
 
1708
    # unmerged things
 
1709
    
1279
1710
    takes_args = ['remote?']
1280
1711
    aliases = ['mis', 'miss']
1281
1712
    # We don't have to add quiet to the list, because 
1282
1713
    # unknown options are parsed as booleans
1283
1714
    takes_options = ['verbose', 'quiet']
1284
1715
 
 
1716
    @display_command
1285
1717
    def run(self, remote=None, verbose=False, quiet=False):
1286
1718
        from bzrlib.errors import BzrCommandError
1287
1719
        from bzrlib.missing import show_missing
1289
1721
        if verbose and quiet:
1290
1722
            raise BzrCommandError('Cannot pass both quiet and verbose')
1291
1723
 
1292
 
        b = find_branch('.')
 
1724
        b = Branch.open_containing('.')[0]
1293
1725
        parent = b.get_parent()
1294
1726
        if remote is None:
1295
1727
            if parent is None:
1299
1731
                    print "Using last location: %s" % parent
1300
1732
                remote = parent
1301
1733
        elif parent is None:
1302
 
            # We only update x-pull if it did not exist, missing should not change the parent
1303
 
            b.controlfile('x-pull', 'wb').write(remote + '\n')
1304
 
        br_remote = find_branch(remote)
1305
 
 
 
1734
            # We only update parent if it did not exist, missing
 
1735
            # should not change the parent
 
1736
            b.set_parent(remote)
 
1737
        br_remote = Branch.open_containing(remote)[0]
1306
1738
        return show_missing(b, br_remote, verbose=verbose, quiet=quiet)
1307
1739
 
1308
1740
 
1309
 
 
1310
1741
class cmd_plugins(Command):
1311
1742
    """List plugins"""
1312
1743
    hidden = True
 
1744
    @display_command
1313
1745
    def run(self):
1314
1746
        import bzrlib.plugin
1315
1747
        from inspect import getdoc
1326
1758
                print '\t', d.split('\n')[0]
1327
1759
 
1328
1760
 
 
1761
class cmd_testament(Command):
 
1762
    """Show testament (signing-form) of a revision."""
 
1763
    takes_options = ['revision', 'long']
 
1764
    takes_args = ['branch?']
 
1765
    @display_command
 
1766
    def run(self, branch='.', revision=None, long=False):
 
1767
        from bzrlib.testament import Testament
 
1768
        b = Branch.open_containing(branch)[0]
 
1769
        b.lock_read()
 
1770
        try:
 
1771
            if revision is None:
 
1772
                rev_id = b.last_revision()
 
1773
            else:
 
1774
                rev_id = revision[0].in_history(b).rev_id
 
1775
            t = Testament.from_revision(b, rev_id)
 
1776
            if long:
 
1777
                sys.stdout.writelines(t.as_text_lines())
 
1778
            else:
 
1779
                sys.stdout.write(t.as_short_text())
 
1780
        finally:
 
1781
            b.unlock()
 
1782
 
 
1783
 
 
1784
class cmd_annotate(Command):
 
1785
    """Show the origin of each line in a file.
 
1786
 
 
1787
    This prints out the given file with an annotation on the left side
 
1788
    indicating which revision, author and date introduced the change.
 
1789
 
 
1790
    If the origin is the same for a run of consecutive lines, it is 
 
1791
    shown only at the top, unless the --all option is given.
 
1792
    """
 
1793
    # TODO: annotate directories; showing when each file was last changed
 
1794
    # TODO: annotate a previous version of a file
 
1795
    # TODO: if the working copy is modified, show annotations on that 
 
1796
    #       with new uncommitted lines marked
 
1797
    aliases = ['blame', 'praise']
 
1798
    takes_args = ['filename']
 
1799
    takes_options = [Option('all', help='show annotations on all lines'),
 
1800
                     Option('long', help='show date in annotations'),
 
1801
                     ]
 
1802
 
 
1803
    @display_command
 
1804
    def run(self, filename, all=False, long=False):
 
1805
        from bzrlib.annotate import annotate_file
 
1806
        b, relpath = Branch.open_containing(filename)
 
1807
        b.lock_read()
 
1808
        try:
 
1809
            tree = WorkingTree(b.base, b)
 
1810
            tree = b.revision_tree(b.last_revision())
 
1811
            file_id = tree.inventory.path2id(relpath)
 
1812
            file_version = tree.inventory[file_id].revision
 
1813
            annotate_file(b, file_version, file_id, long, all, sys.stdout)
 
1814
        finally:
 
1815
            b.unlock()
 
1816
 
 
1817
 
 
1818
class cmd_re_sign(Command):
 
1819
    """Create a digital signature for an existing revision."""
 
1820
    # TODO be able to replace existing ones.
 
1821
 
 
1822
    hidden = True # is this right ?
 
1823
    takes_args = ['revision_id?']
 
1824
    takes_options = ['revision']
 
1825
    
 
1826
    def run(self, revision_id=None, revision=None):
 
1827
        import bzrlib.config as config
 
1828
        import bzrlib.gpg as gpg
 
1829
        if revision_id is not None and revision is not None:
 
1830
            raise BzrCommandError('You can only supply one of revision_id or --revision')
 
1831
        if revision_id is None and revision is None:
 
1832
            raise BzrCommandError('You must supply either --revision or a revision_id')
 
1833
        b = Branch.open_containing('.')[0]
 
1834
        gpg_strategy = gpg.GPGStrategy(config.BranchConfig(b))
 
1835
        if revision_id is not None:
 
1836
            b.sign_revision(revision_id, gpg_strategy)
 
1837
        elif revision is not None:
 
1838
            if len(revision) == 1:
 
1839
                revno, rev_id = revision[0].in_history(b)
 
1840
                b.sign_revision(rev_id, gpg_strategy)
 
1841
            elif len(revision) == 2:
 
1842
                # are they both on rh- if so we can walk between them
 
1843
                # might be nice to have a range helper for arbitrary
 
1844
                # revision paths. hmm.
 
1845
                from_revno, from_revid = revision[0].in_history(b)
 
1846
                to_revno, to_revid = revision[1].in_history(b)
 
1847
                if to_revid is None:
 
1848
                    to_revno = b.revno()
 
1849
                if from_revno is None or to_revno is None:
 
1850
                    raise BzrCommandError('Cannot sign a range of non-revision-history revisions')
 
1851
                for revno in range(from_revno, to_revno + 1):
 
1852
                    b.sign_revision(b.get_rev_id(revno), gpg_strategy)
 
1853
            else:
 
1854
                raise BzrCommandError('Please supply either one revision, or a range.')
 
1855
 
 
1856
class cmd_bind(Command):
 
1857
    """Bind the current branch to its parent.
 
1858
 
 
1859
    After binding, commits must succeed on the parent branch
 
1860
    before they can be done on the local one.
 
1861
    """
 
1862
 
 
1863
    takes_args = ['location?']
 
1864
    takes_options = []
 
1865
 
 
1866
    def run(self, location=None):
 
1867
        b, relpath = Branch.open_containing('.')
 
1868
        if location is None:
 
1869
            location = b.get_bound_location()
 
1870
        if location is None:
 
1871
            location = b.get_parent()
 
1872
        if location is None:
 
1873
            raise BzrCommandError('Branch has no parent,'
 
1874
                                  ' you must supply a bind location.')
 
1875
        b_other = Branch.open(location)
 
1876
        try:
 
1877
            b.bind(b_other)
 
1878
        except DivergedBranches:
 
1879
            raise BzrCommandError('These branches have diverged.'
 
1880
                                  ' Try merging, and then bind again.')
 
1881
 
 
1882
class cmd_unbind(Command):
 
1883
    """Bind the current branch to its parent.
 
1884
 
 
1885
    After unbinding, the local branch is considered independent.
 
1886
    """
 
1887
 
 
1888
    takes_args = []
 
1889
    takes_options = []
 
1890
 
 
1891
    def run(self):
 
1892
        b, relpath = Branch.open_containing('.')
 
1893
        b.unbind()
 
1894
 
 
1895
class cmd_update(Command):
 
1896
    """Update the local tree for checkouts and bound branches.
 
1897
    """
 
1898
    def run(self):
 
1899
        br_local, relpath = Branch.open_containing('.')
 
1900
        # TODO: Check here to see if this is a checkout
 
1901
        bound_loc = br_local.get_bound_location()
 
1902
        if not bound_loc:
 
1903
            raise BzrCommandError('Branch %s is not a checkout or a bound branch,'
 
1904
                                  ' you probably want pull' % br_local.base)
 
1905
 
 
1906
        br_bound = Branch.open(bound_loc)
 
1907
        try:
 
1908
            br_local.working_tree().pull(br_bound, overwrite=False)
 
1909
        except DivergedBranches:
 
1910
            raise BzrCommandError("These branches have diverged."
 
1911
                                  "  Try merge.")
 
1912
 
 
1913
 
 
1914
# these get imported and then picked up by the scan for cmd_*
 
1915
# TODO: Some more consistent way to split command definitions across files;
 
1916
# we do need to load at least some information about them to know of 
 
1917
# aliases.
 
1918
from bzrlib.conflicts import cmd_resolve, cmd_conflicts, restore