~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
from bzrlib.tree import EmptyTree, RevisionTree
45
45
from bzrlib.inventory import Inventory
46
46
from bzrlib.store import copy_all
47
 
from bzrlib.store.compressed_text import CompressedTextStore
48
47
from bzrlib.store.text import TextStore
49
48
from bzrlib.store.weave import WeaveStore
50
49
from bzrlib.testament import Testament
52
51
from bzrlib.transport import Transport, get_transport
53
52
import bzrlib.xml5
54
53
import bzrlib.ui
 
54
from config import TreeConfig
55
55
 
56
56
 
57
57
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
111
111
        """Open a branch which may be of an old format.
112
112
        
113
113
        Only local branches are supported."""
114
 
        return _Branch(get_transport(base), relax_version_check=True)
 
114
        return BzrBranch(get_transport(base), relax_version_check=True)
115
115
        
116
116
    @staticmethod
117
117
    def open(base):
118
118
        """Open an existing branch, rooted at 'base' (url)"""
119
119
        t = get_transport(base)
120
120
        mutter("trying to open %r with transport %r", base, t)
121
 
        return _Branch(t)
 
121
        return BzrBranch(t)
122
122
 
123
123
    @staticmethod
124
124
    def open_containing(url):
133
133
        t = get_transport(url)
134
134
        while True:
135
135
            try:
136
 
                return _Branch(t), t.relpath(url)
 
136
                return BzrBranch(t), t.relpath(url)
137
137
            except NotBranchError:
138
138
                pass
139
139
            new_t = t.clone('..')
146
146
    def initialize(base):
147
147
        """Create a new branch, rooted at 'base' (url)"""
148
148
        t = get_transport(base)
149
 
        return _Branch(t, init=True)
 
149
        return BzrBranch(t, init=True)
150
150
 
151
151
    def setup_caching(self, cache_root):
152
152
        """Subclasses that care about caching should override this, and set
154
154
        """
155
155
        self.cache_root = cache_root
156
156
 
157
 
 
158
 
class _Branch(Branch):
 
157
    def _get_nick(self):
 
158
        cfg = self.tree_config()
 
159
        return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
 
160
 
 
161
    def _set_nick(self, nick):
 
162
        cfg = self.tree_config()
 
163
        cfg.set_option(nick, "nickname")
 
164
        assert cfg.get_option("nickname") == nick
 
165
 
 
166
    nick = property(_get_nick, _set_nick)
 
167
        
 
168
    def push_stores(self, branch_to):
 
169
        """Copy the content of this branches store to branch_to."""
 
170
        raise NotImplementedError('push_stores is abstract')
 
171
 
 
172
    def get_transaction(self):
 
173
        """Return the current active transaction.
 
174
 
 
175
        If no transaction is active, this returns a passthrough object
 
176
        for which all data is immediately flushed and no caching happens.
 
177
        """
 
178
        raise NotImplementedError('get_transaction is abstract')
 
179
 
 
180
    def lock_write(self):
 
181
        raise NotImplementedError('lock_write is abstract')
 
182
        
 
183
    def lock_read(self):
 
184
        raise NotImplementedError('lock_read is abstract')
 
185
 
 
186
    def unlock(self):
 
187
        raise NotImplementedError('unlock is abstract')
 
188
 
 
189
    def abspath(self, name):
 
190
        """Return absolute filename for something in the branch
 
191
        
 
192
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
193
        method and not a tree method.
 
194
        """
 
195
        raise NotImplementedError('abspath is abstract')
 
196
 
 
197
    def controlfilename(self, file_or_path):
 
198
        """Return location relative to branch."""
 
199
        raise NotImplementedError('controlfilename is abstract')
 
200
 
 
201
    def controlfile(self, file_or_path, mode='r'):
 
202
        """Open a control file for this branch.
 
203
 
 
204
        There are two classes of file in the control directory: text
 
205
        and binary.  binary files are untranslated byte streams.  Text
 
206
        control files are stored with Unix newlines and in UTF-8, even
 
207
        if the platform or locale defaults are different.
 
208
 
 
209
        Controlfiles should almost never be opened in write mode but
 
210
        rather should be atomically copied and replaced using atomicfile.
 
211
        """
 
212
        raise NotImplementedError('controlfile is abstract')
 
213
 
 
214
    def put_controlfile(self, path, f, encode=True):
 
215
        """Write an entry as a controlfile.
 
216
 
 
217
        :param path: The path to put the file, relative to the .bzr control
 
218
                     directory
 
219
        :param f: A file-like or string object whose contents should be copied.
 
220
        :param encode:  If true, encode the contents as utf-8
 
221
        """
 
222
        raise NotImplementedError('put_controlfile is abstract')
 
223
 
 
224
    def put_controlfiles(self, files, encode=True):
 
225
        """Write several entries as controlfiles.
 
226
 
 
227
        :param files: A list of [(path, file)] pairs, where the path is the directory
 
228
                      underneath the bzr control directory
 
229
        :param encode:  If true, encode the contents as utf-8
 
230
        """
 
231
        raise NotImplementedError('put_controlfiles is abstract')
 
232
 
 
233
    def get_root_id(self):
 
234
        """Return the id of this branches root"""
 
235
        raise NotImplementedError('get_root_id is abstract')
 
236
 
 
237
    def set_root_id(self, file_id):
 
238
        raise NotImplementedError('set_root_id is abstract')
 
239
 
 
240
    def add(self, files, ids=None):
 
241
        """Make files versioned.
 
242
 
 
243
        Note that the command line normally calls smart_add instead,
 
244
        which can automatically recurse.
 
245
 
 
246
        This puts the files in the Added state, so that they will be
 
247
        recorded by the next commit.
 
248
 
 
249
        files
 
250
            List of paths to add, relative to the base of the tree.
 
251
 
 
252
        ids
 
253
            If set, use these instead of automatically generated ids.
 
254
            Must be the same length as the list of files, but may
 
255
            contain None for ids that are to be autogenerated.
 
256
 
 
257
        TODO: Perhaps have an option to add the ids even if the files do
 
258
              not (yet) exist.
 
259
 
 
260
        TODO: Perhaps yield the ids and paths as they're added.
 
261
        """
 
262
        raise NotImplementedError('add is abstract')
 
263
 
 
264
    def print_file(self, file, revno):
 
265
        """Print `file` to stdout."""
 
266
        raise NotImplementedError('print_file is abstract')
 
267
 
 
268
    def unknowns(self):
 
269
        """Return all unknown files.
 
270
 
 
271
        These are files in the working directory that are not versioned or
 
272
        control files or ignored.
 
273
        
 
274
        >>> from bzrlib.workingtree import WorkingTree
 
275
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
276
        >>> map(str, b.unknowns())
 
277
        ['foo']
 
278
        >>> b.add('foo')
 
279
        >>> list(b.unknowns())
 
280
        []
 
281
        >>> WorkingTree(b.base, b).remove('foo')
 
282
        >>> list(b.unknowns())
 
283
        [u'foo']
 
284
        """
 
285
        raise NotImplementedError('unknowns is abstract')
 
286
 
 
287
    def append_revision(self, *revision_ids):
 
288
        raise NotImplementedError('append_revision is abstract')
 
289
 
 
290
    def set_revision_history(self, rev_history):
 
291
        raise NotImplementedError('set_revision_history is abstract')
 
292
 
 
293
    def has_revision(self, revision_id):
 
294
        """True if this branch has a copy of the revision.
 
295
 
 
296
        This does not necessarily imply the revision is merge
 
297
        or on the mainline."""
 
298
        raise NotImplementedError('has_revision is abstract')
 
299
 
 
300
    def get_revision_xml_file(self, revision_id):
 
301
        """Return XML file object for revision object."""
 
302
        raise NotImplementedError('get_revision_xml_file is abstract')
 
303
 
 
304
    def get_revision_xml(self, revision_id):
 
305
        raise NotImplementedError('get_revision_xml is abstract')
 
306
 
 
307
    def get_revision(self, revision_id):
 
308
        """Return the Revision object for a named revision"""
 
309
        raise NotImplementedError('get_revision is abstract')
 
310
 
 
311
    def get_revision_delta(self, revno):
 
312
        """Return the delta for one revision.
 
313
 
 
314
        The delta is relative to its mainline predecessor, or the
 
315
        empty tree for revision 1.
 
316
        """
 
317
        assert isinstance(revno, int)
 
318
        rh = self.revision_history()
 
319
        if not (1 <= revno <= len(rh)):
 
320
            raise InvalidRevisionNumber(revno)
 
321
 
 
322
        # revno is 1-based; list is 0-based
 
323
 
 
324
        new_tree = self.revision_tree(rh[revno-1])
 
325
        if revno == 1:
 
326
            old_tree = EmptyTree()
 
327
        else:
 
328
            old_tree = self.revision_tree(rh[revno-2])
 
329
 
 
330
        return compare_trees(old_tree, new_tree)
 
331
 
 
332
    def get_revision_sha1(self, revision_id):
 
333
        """Hash the stored value of a revision, and return it."""
 
334
        raise NotImplementedError('get_revision_sha1 is abstract')
 
335
 
 
336
    def get_ancestry(self, revision_id):
 
337
        """Return a list of revision-ids integrated by a revision.
 
338
        
 
339
        This currently returns a list, but the ordering is not guaranteed:
 
340
        treat it as a set.
 
341
        """
 
342
        raise NotImplementedError('get_ancestry is abstract')
 
343
 
 
344
    def get_inventory(self, revision_id):
 
345
        """Get Inventory object by hash."""
 
346
        raise NotImplementedError('get_inventory is abstract')
 
347
 
 
348
    def get_inventory_xml(self, revision_id):
 
349
        """Get inventory XML as a file object."""
 
350
        raise NotImplementedError('get_inventory_xml is abstract')
 
351
 
 
352
    def get_inventory_sha1(self, revision_id):
 
353
        """Return the sha1 hash of the inventory entry."""
 
354
        raise NotImplementedError('get_inventory_sha1 is abstract')
 
355
 
 
356
    def get_revision_inventory(self, revision_id):
 
357
        """Return inventory of a past revision."""
 
358
        raise NotImplementedError('get_revision_inventory is abstract')
 
359
 
 
360
    def revision_history(self):
 
361
        """Return sequence of revision hashes on to this branch."""
 
362
        raise NotImplementedError('revision_history is abstract')
 
363
 
 
364
    def revno(self):
 
365
        """Return current revision number for this branch.
 
366
 
 
367
        That is equivalent to the number of revisions committed to
 
368
        this branch.
 
369
        """
 
370
        return len(self.revision_history())
 
371
 
 
372
    def last_revision(self):
 
373
        """Return last patch hash, or None if no history."""
 
374
        ph = self.revision_history()
 
375
        if ph:
 
376
            return ph[-1]
 
377
        else:
 
378
            return None
 
379
 
 
380
    def missing_revisions(self, other, stop_revision=None, other_history=None):
 
381
        """Return a list of new revisions that would perfectly fit.
 
382
        
 
383
        If self and other have not diverged, return a list of the revisions
 
384
        present in other, but missing from self.
 
385
 
 
386
        >>> from bzrlib.commit import commit
 
387
        >>> bzrlib.trace.silent = True
 
388
        >>> br1 = ScratchBranch()
 
389
        >>> br2 = ScratchBranch()
 
390
        >>> br1.missing_revisions(br2)
 
391
        []
 
392
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
393
        >>> br1.missing_revisions(br2)
 
394
        [u'REVISION-ID-1']
 
395
        >>> br2.missing_revisions(br1)
 
396
        []
 
397
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
398
        >>> br1.missing_revisions(br2)
 
399
        []
 
400
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
401
        >>> br1.missing_revisions(br2)
 
402
        [u'REVISION-ID-2A']
 
403
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
404
        >>> br1.missing_revisions(br2)
 
405
        Traceback (most recent call last):
 
406
        DivergedBranches: These branches have diverged.
 
407
        """
 
408
        self_history = self.revision_history()
 
409
        self_len = len(self_history)
 
410
        if other_history is None:
 
411
            other_history = other.revision_history()
 
412
        other_len = len(other_history)
 
413
        common_index = min(self_len, other_len) -1
 
414
        if common_index >= 0 and \
 
415
            self_history[common_index] != other_history[common_index]:
 
416
            raise DivergedBranches(self, other)
 
417
 
 
418
        if stop_revision is None:
 
419
            stop_revision = other_len
 
420
        else:
 
421
            assert isinstance(stop_revision, int)
 
422
            if stop_revision > other_len:
 
423
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
424
        return other_history[self_len:stop_revision]
 
425
    
 
426
    def update_revisions(self, other, stop_revision=None, other_history=None):
 
427
        """Pull in new perfect-fit revisions.
 
428
 
 
429
        :param other: Another Branch to pull from
 
430
        :param stop_revision: Updated until the given revision
 
431
        :param other_history: Alternative history to other.revision_history()
 
432
        :return: None
 
433
        """
 
434
        raise NotImplementedError('update_revisions is abstract')
 
435
 
 
436
    def pullable_revisions(self, other, stop_revision, other_history=None):
 
437
        raise NotImplementedError('pullable_revisions is abstract')
 
438
        
 
439
    def revision_id_to_revno(self, revision_id):
 
440
        """Given a revision id, return its revno"""
 
441
        if revision_id is None:
 
442
            return 0
 
443
        history = self.revision_history()
 
444
        try:
 
445
            return history.index(revision_id) + 1
 
446
        except ValueError:
 
447
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
448
 
 
449
    def get_rev_id(self, revno, history=None):
 
450
        """Find the revision id of the specified revno."""
 
451
        if revno == 0:
 
452
            return None
 
453
        if history is None:
 
454
            history = self.revision_history()
 
455
        elif revno <= 0 or revno > len(history):
 
456
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
457
        return history[revno - 1]
 
458
 
 
459
    def revision_tree(self, revision_id):
 
460
        """Return Tree for a revision on this branch.
 
461
 
 
462
        `revision_id` may be None for the null revision, in which case
 
463
        an `EmptyTree` is returned."""
 
464
        raise NotImplementedError('revision_tree is abstract')
 
465
 
 
466
    def working_tree(self):
 
467
        """Return a `Tree` for the working copy."""
 
468
        raise NotImplementedError('working_tree is abstract')
 
469
 
 
470
    def pull(self, source, overwrite=False):
 
471
        raise NotImplementedError('pull is abstract')
 
472
 
 
473
    def basis_tree(self):
 
474
        """Return `Tree` object for last revision.
 
475
 
 
476
        If there are no revisions yet, return an `EmptyTree`.
 
477
        """
 
478
        return self.revision_tree(self.last_revision())
 
479
 
 
480
    def rename_one(self, from_rel, to_rel):
 
481
        """Rename one file.
 
482
 
 
483
        This can change the directory or the filename or both.
 
484
        """
 
485
        raise NotImplementedError('rename_one is abstract')
 
486
 
 
487
    def move(self, from_paths, to_name):
 
488
        """Rename files.
 
489
 
 
490
        to_name must exist as a versioned directory.
 
491
 
 
492
        If to_name exists and is a directory, the files are moved into
 
493
        it, keeping their old names.  If it is a directory, 
 
494
 
 
495
        Note that to_name is only the last component of the new name;
 
496
        this doesn't change the directory.
 
497
 
 
498
        This returns a list of (from_path, to_path) pairs for each
 
499
        entry that is moved.
 
500
        """
 
501
        raise NotImplementedError('move is abstract')
 
502
 
 
503
    def revert(self, filenames, old_tree=None, backups=True):
 
504
        """Restore selected files to the versions from a previous tree.
 
505
 
 
506
        backups
 
507
            If true (default) backups are made of files before
 
508
            they're renamed.
 
509
        """
 
510
        raise NotImplementedError('revert is abstract')
 
511
 
 
512
    def pending_merges(self):
 
513
        """Return a list of pending merges.
 
514
 
 
515
        These are revisions that have been merged into the working
 
516
        directory but not yet committed.
 
517
        """
 
518
        raise NotImplementedError('pending_merges is abstract')
 
519
 
 
520
    def add_pending_merge(self, *revision_ids):
 
521
        # TODO: Perhaps should check at this point that the
 
522
        # history of the revision is actually present?
 
523
        raise NotImplementedError('add_pending_merge is abstract')
 
524
 
 
525
    def set_pending_merges(self, rev_list):
 
526
        raise NotImplementedError('set_pending_merges is abstract')
 
527
 
 
528
    def get_parent(self):
 
529
        """Return the parent location of the branch.
 
530
 
 
531
        This is the default location for push/pull/missing.  The usual
 
532
        pattern is that the user can override it by specifying a
 
533
        location.
 
534
        """
 
535
        raise NotImplementedError('get_parent is abstract')
 
536
 
 
537
    def get_push_location(self):
 
538
        """Return the None or the location to push this branch to."""
 
539
        raise NotImplementedError('get_push_location is abstract')
 
540
 
 
541
    def set_push_location(self, location):
 
542
        """Set a new push location for this branch."""
 
543
        raise NotImplementedError('set_push_location is abstract')
 
544
 
 
545
    def set_parent(self, url):
 
546
        raise NotImplementedError('set_parent is abstract')
 
547
 
 
548
    def check_revno(self, revno):
 
549
        """\
 
550
        Check whether a revno corresponds to any revision.
 
551
        Zero (the NULL revision) is considered valid.
 
552
        """
 
553
        if revno != 0:
 
554
            self.check_real_revno(revno)
 
555
            
 
556
    def check_real_revno(self, revno):
 
557
        """\
 
558
        Check whether a revno corresponds to a real revision.
 
559
        Zero (the NULL revision) is considered invalid
 
560
        """
 
561
        if revno < 1 or revno > self.revno():
 
562
            raise InvalidRevisionNumber(revno)
 
563
        
 
564
    def sign_revision(self, revision_id, gpg_strategy):
 
565
        raise NotImplementedError('sign_revision is abstract')
 
566
 
 
567
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
568
        raise NotImplementedError('store_revision_signature is abstract')
 
569
 
 
570
class BzrBranch(Branch):
159
571
    """A branch stored in the actual filesystem.
160
572
 
161
573
    Note that it's "local" in the context of the filesystem; it doesn't
187
599
    REVISION_NAMESPACES = {}
188
600
 
189
601
    def push_stores(self, branch_to):
190
 
        """Copy the content of this branches store to branch_to."""
 
602
        """See Branch.push_stores."""
191
603
        if (self._branch_format != branch_to._branch_format
192
604
            or self._branch_format != 4):
193
605
            from bzrlib.fetch import greedy_fetch
237
649
            # some existing branches where there's a mixture; we probably 
238
650
            # still want the option to look for both.
239
651
            relpath = self._rel_controlfilename(name)
240
 
            if compressed:
241
 
                store = CompressedTextStore(self._transport.clone(relpath),
242
 
                                            prefixed=prefixed)
243
 
            else:
244
 
                store = TextStore(self._transport.clone(relpath),
245
 
                                  prefixed=prefixed)
 
652
            store = TextStore(self._transport.clone(relpath),
 
653
                              prefixed=prefixed,
 
654
                              compressed=compressed)
246
655
            #if self._transport.should_cache():
247
656
            #    cache_path = os.path.join(self.cache_root, name)
248
657
            #    os.mkdir(cache_path)
313
722
        transaction.finish()
314
723
 
315
724
    def get_transaction(self):
316
 
        """Return the current active transaction.
317
 
 
318
 
        If no transaction is active, this returns a passthrough object
319
 
        for which all data is immediately flushed and no caching happens.
320
 
        """
 
725
        """See Branch.get_transaction."""
321
726
        if self._transaction is None:
322
727
            return transactions.PassThroughTransaction()
323
728
        else:
375
780
            self._lock_mode = self._lock_count = None
376
781
 
377
782
    def abspath(self, name):
378
 
        """Return absolute filename for something in the branch
379
 
        
380
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
381
 
        method and not a tree method.
382
 
        """
 
783
        """See Branch.abspath."""
383
784
        return self._transport.abspath(name)
384
785
 
385
786
    def _rel_controlfilename(self, file_or_path):
390
791
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
391
792
 
392
793
    def controlfilename(self, file_or_path):
393
 
        """Return location relative to branch."""
 
794
        """See Branch.controlfilename."""
394
795
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
395
796
 
396
797
    def controlfile(self, file_or_path, mode='r'):
397
 
        """Open a control file for this branch.
398
 
 
399
 
        There are two classes of file in the control directory: text
400
 
        and binary.  binary files are untranslated byte streams.  Text
401
 
        control files are stored with Unix newlines and in UTF-8, even
402
 
        if the platform or locale defaults are different.
403
 
 
404
 
        Controlfiles should almost never be opened in write mode but
405
 
        rather should be atomically copied and replaced using atomicfile.
406
 
        """
 
798
        """See Branch.controlfile."""
407
799
        import codecs
408
800
 
409
801
        relpath = self._rel_controlfilename(file_or_path)
425
817
            raise BzrError("invalid controlfile mode %r" % mode)
426
818
 
427
819
    def put_controlfile(self, path, f, encode=True):
428
 
        """Write an entry as a controlfile.
429
 
 
430
 
        :param path: The path to put the file, relative to the .bzr control
431
 
                     directory
432
 
        :param f: A file-like or string object whose contents should be copied.
433
 
        :param encode:  If true, encode the contents as utf-8
434
 
        """
 
820
        """See Branch.put_controlfile."""
435
821
        self.put_controlfiles([(path, f)], encode=encode)
436
822
 
437
823
    def put_controlfiles(self, files, encode=True):
438
 
        """Write several entries as controlfiles.
439
 
 
440
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
441
 
                      underneath the bzr control directory
442
 
        :param encode:  If true, encode the contents as utf-8
443
 
        """
 
824
        """See Branch.put_controlfiles."""
444
825
        import codecs
445
826
        ctrl_files = []
446
827
        for path, f in files:
517
898
                            ' and "bzr init" again'])
518
899
 
519
900
    def get_root_id(self):
520
 
        """Return the id of this branches root"""
 
901
        """See Branch.get_root_id."""
521
902
        inv = self.get_inventory(self.last_revision())
522
903
        return inv.root.file_id
523
904
 
524
905
    @needs_write_lock
 
906
    def set_root_id(self, file_id):
 
907
        """See Branch.set_root_id."""
 
908
        inv = self.working_tree().read_working_inventory()
 
909
        orig_root_id = inv.root.file_id
 
910
        del inv._byid[inv.root.file_id]
 
911
        inv.root.file_id = file_id
 
912
        inv._byid[inv.root.file_id] = inv.root
 
913
        for fid in inv:
 
914
            entry = inv[fid]
 
915
            if entry.parent_id in (None, orig_root_id):
 
916
                entry.parent_id = inv.root.file_id
 
917
        self._write_inventory(inv)
 
918
 
 
919
    @needs_write_lock
525
920
    def add(self, files, ids=None):
526
 
        """Make files versioned.
527
 
 
528
 
        Note that the command line normally calls smart_add instead,
529
 
        which can automatically recurse.
530
 
 
531
 
        This puts the files in the Added state, so that they will be
532
 
        recorded by the next commit.
533
 
 
534
 
        files
535
 
            List of paths to add, relative to the base of the tree.
536
 
 
537
 
        ids
538
 
            If set, use these instead of automatically generated ids.
539
 
            Must be the same length as the list of files, but may
540
 
            contain None for ids that are to be autogenerated.
541
 
 
542
 
        TODO: Perhaps have an option to add the ids even if the files do
543
 
              not (yet) exist.
544
 
 
545
 
        TODO: Perhaps yield the ids and paths as they're added.
546
 
        """
 
921
        """See Branch.add."""
547
922
        # TODO: Re-adding a file that is removed in the working copy
548
923
        # should probably put it back with the previous ID.
549
924
        if isinstance(files, basestring):
589
964
 
590
965
    @needs_read_lock
591
966
    def print_file(self, file, revno):
592
 
        """Print `file` to stdout."""
 
967
        """See Branch.print_file."""
593
968
        tree = self.revision_tree(self.get_rev_id(revno))
594
969
        # use inventory as it was in that revision
595
970
        file_id = tree.inventory.path2id(file)
598
973
        tree.print_file(file_id)
599
974
 
600
975
    def unknowns(self):
601
 
        """Return all unknown files.
602
 
 
603
 
        These are files in the working directory that are not versioned or
604
 
        control files or ignored.
605
 
        
606
 
        >>> from bzrlib.workingtree import WorkingTree
607
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
608
 
        >>> map(str, b.unknowns())
609
 
        ['foo']
610
 
        >>> b.add('foo')
611
 
        >>> list(b.unknowns())
612
 
        []
613
 
        >>> WorkingTree(b.base, b).remove('foo')
614
 
        >>> list(b.unknowns())
615
 
        [u'foo']
616
 
        """
 
976
        """See Branch.unknowns."""
617
977
        return self.working_tree().unknowns()
618
978
 
619
979
    @needs_write_lock
620
980
    def append_revision(self, *revision_ids):
 
981
        """See Branch.append_revision."""
621
982
        for revision_id in revision_ids:
622
983
            mutter("add {%s} to revision-history" % revision_id)
623
984
        rev_history = self.revision_history()
626
987
 
627
988
    @needs_write_lock
628
989
    def set_revision_history(self, rev_history):
 
990
        """See Branch.set_revision_history."""
629
991
        bound_loc = self.get_bound_location()
630
992
        if bound_loc is not None:
631
993
            # TODO: At this point, we could get a NotBranchError
641
1003
        self.put_controlfile('revision-history', '\n'.join(rev_history))
642
1004
 
643
1005
    def has_revision(self, revision_id):
644
 
        """True if this branch has a copy of the revision.
645
 
 
646
 
        This does not necessarily imply the revision is merge
647
 
        or on the mainline."""
 
1006
        """See Branch.has_revision."""
648
1007
        return (revision_id is None
649
1008
                or self.revision_store.has_id(revision_id))
650
1009
 
651
1010
    @needs_read_lock
652
1011
    def get_revision_xml_file(self, revision_id):
653
 
        """Return XML file object for revision object."""
 
1012
        """See Branch.get_revision_xml_file."""
654
1013
        if not revision_id or not isinstance(revision_id, basestring):
655
1014
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
656
1015
        try:
662
1021
    get_revision_xml = get_revision_xml_file
663
1022
 
664
1023
    def get_revision_xml(self, revision_id):
 
1024
        """See Branch.get_revision_xml."""
665
1025
        return self.get_revision_xml_file(revision_id).read()
666
1026
 
667
1027
 
668
1028
    def get_revision(self, revision_id):
669
 
        """Return the Revision object for a named revision"""
 
1029
        """See Branch.get_revision."""
670
1030
        xml_file = self.get_revision_xml_file(revision_id)
671
1031
 
672
1032
        try:
679
1039
        assert r.revision_id == revision_id
680
1040
        return r
681
1041
 
682
 
    def get_revision_delta(self, revno):
683
 
        """Return the delta for one revision.
684
 
 
685
 
        The delta is relative to its mainline predecessor, or the
686
 
        empty tree for revision 1.
687
 
        """
688
 
        assert isinstance(revno, int)
689
 
        rh = self.revision_history()
690
 
        if not (1 <= revno <= len(rh)):
691
 
            raise InvalidRevisionNumber(revno)
692
 
 
693
 
        # revno is 1-based; list is 0-based
694
 
 
695
 
        new_tree = self.revision_tree(rh[revno-1])
696
 
        if revno == 1:
697
 
            old_tree = EmptyTree()
698
 
        else:
699
 
            old_tree = self.revision_tree(rh[revno-2])
700
 
 
701
 
        return compare_trees(old_tree, new_tree)
702
 
 
703
1042
    def get_revision_sha1(self, revision_id):
704
 
        """Hash the stored value of a revision, and return it."""
 
1043
        """See Branch.get_revision_sha1."""
705
1044
        # In the future, revision entries will be signed. At that
706
1045
        # point, it is probably best *not* to include the signature
707
1046
        # in the revision hash. Because that lets you re-sign
711
1050
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
712
1051
 
713
1052
    def get_ancestry(self, revision_id):
714
 
        """Return a list of revision-ids integrated by a revision.
715
 
        
716
 
        This currently returns a list, but the ordering is not guaranteed:
717
 
        treat it as a set.
718
 
        """
 
1053
        """See Branch.get_ancestry."""
719
1054
        if revision_id is None:
720
1055
            return [None]
721
 
        w = self.get_inventory_weave()
 
1056
        w = self._get_inventory_weave()
722
1057
        return [None] + map(w.idx_to_name,
723
1058
                            w.inclusions([w.lookup(revision_id)]))
724
1059
 
725
 
    def get_inventory_weave(self):
 
1060
    def _get_inventory_weave(self):
726
1061
        return self.control_weaves.get_weave('inventory',
727
1062
                                             self.get_transaction())
728
1063
 
729
1064
    def get_inventory(self, revision_id):
730
 
        """Get Inventory object by hash."""
 
1065
        """See Branch.get_inventory."""
731
1066
        xml = self.get_inventory_xml(revision_id)
732
1067
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
733
1068
 
734
1069
    def get_inventory_xml(self, revision_id):
735
 
        """Get inventory XML as a file object."""
 
1070
        """See Branch.get_inventory_xml."""
736
1071
        try:
737
1072
            assert isinstance(revision_id, basestring), type(revision_id)
738
 
            iw = self.get_inventory_weave()
 
1073
            iw = self._get_inventory_weave()
739
1074
            return iw.get_text(iw.lookup(revision_id))
740
1075
        except IndexError:
741
1076
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
742
1077
 
743
1078
    def get_inventory_sha1(self, revision_id):
744
 
        """Return the sha1 hash of the inventory entry
745
 
        """
 
1079
        """See Branch.get_inventory_sha1."""
746
1080
        return self.get_revision(revision_id).inventory_sha1
747
1081
 
748
1082
    def get_revision_inventory(self, revision_id):
749
 
        """Return inventory of a past revision."""
 
1083
        """See Branch.get_revision_inventory."""
750
1084
        # TODO: Unify this with get_inventory()
751
1085
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
752
1086
        # must be the same as its revision, so this is trivial.
762
1096
 
763
1097
    @needs_read_lock
764
1098
    def revision_history(self):
765
 
        """Return sequence of revision hashes on to this branch."""
 
1099
        """See Branch.revision_history."""
766
1100
        transaction = self.get_transaction()
767
1101
        history = transaction.map.find_revision_history()
768
1102
        if history is not None:
776
1110
        # transaction.register_clean(history, precious=True)
777
1111
        return list(history)
778
1112
 
779
 
    def revno(self):
780
 
        """Return current revision number for this branch.
781
 
 
782
 
        That is equivalent to the number of revisions committed to
783
 
        this branch.
784
 
        """
785
 
        return len(self.revision_history())
786
 
 
787
 
    def last_revision(self):
788
 
        """Return last patch hash, or None if no history.
789
 
        """
790
 
        ph = self.revision_history()
791
 
        if ph:
792
 
            return ph[-1]
793
 
        else:
794
 
            return None
795
 
 
796
 
    def missing_revisions(self, other, stop_revision=None, other_history=None):
797
 
        """Return a list of new revisions that would perfectly fit.
798
 
        
799
 
        If self and other have not diverged, return a list of the revisions
800
 
        present in other, but missing from self.
801
 
 
802
 
        >>> from bzrlib.commit import commit
803
 
        >>> bzrlib.trace.silent = True
804
 
        >>> br1 = ScratchBranch()
805
 
        >>> br2 = ScratchBranch()
806
 
        >>> br1.missing_revisions(br2)
807
 
        []
808
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
809
 
        >>> br1.missing_revisions(br2)
810
 
        [u'REVISION-ID-1']
811
 
        >>> br2.missing_revisions(br1)
812
 
        []
813
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
814
 
        >>> br1.missing_revisions(br2)
815
 
        []
816
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
817
 
        >>> br1.missing_revisions(br2)
818
 
        [u'REVISION-ID-2A']
819
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
820
 
        >>> br1.missing_revisions(br2)
821
 
        Traceback (most recent call last):
822
 
        DivergedBranches: These branches have diverged.
823
 
        """
824
 
        self_history = self.revision_history()
825
 
        self_len = len(self_history)
826
 
        if other_history is None:
827
 
            other_history = other.revision_history()
828
 
        other_len = len(other_history)
829
 
        common_index = min(self_len, other_len) -1
830
 
        if common_index >= 0 and \
831
 
            self_history[common_index] != other_history[common_index]:
832
 
            raise DivergedBranches(self, other)
833
 
 
834
 
        if stop_revision is None:
835
 
            stop_revision = other_len
836
 
        else:
837
 
            assert isinstance(stop_revision, int)
838
 
            if stop_revision > other_len:
839
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
840
 
        return other_history[self_len:stop_revision]
841
 
 
842
1113
    def update_revisions(self, other, stop_revision=None, other_history=None):
843
 
        """Pull in new perfect-fit revisions.
844
 
 
845
 
        :param other: Another Branch to pull from
846
 
        :param stop_revision: Updated until the given revision
847
 
        :param other_history: Alternative history to other.revision_history()
848
 
        :return: None
849
 
        """
 
1114
        """See Branch.update_revisions."""
850
1115
        from bzrlib.fetch import greedy_fetch
851
1116
        if stop_revision is None:
852
1117
            if other_history is not None:
908
1173
        return history[revno - 1]
909
1174
 
910
1175
    def revision_tree(self, revision_id):
911
 
        """Return Tree for a revision on this branch.
912
 
 
913
 
        `revision_id` may be None for the null revision, in which case
914
 
        an `EmptyTree` is returned."""
 
1176
        """See Branch.revision_tree."""
915
1177
        # TODO: refactor this to use an existing revision object
916
1178
        # so we don't need to read it in twice.
917
1179
        if revision_id == None or revision_id == NULL_REVISION:
921
1183
            return RevisionTree(self.weave_store, inv, revision_id)
922
1184
 
923
1185
    def working_tree(self):
924
 
        """Return a `Tree` for the working copy."""
 
1186
        """See Branch.working_tree."""
925
1187
        from bzrlib.workingtree import WorkingTree
926
1188
        # TODO: In the future, perhaps WorkingTree should utilize Transport
927
1189
        # RobertCollins 20051003 - I don't think it should - working trees are
934
1196
 
935
1197
    @needs_write_lock
936
1198
    def pull(self, source, overwrite=False):
 
1199
        """See Branch.pull."""
937
1200
        source.lock_read()
938
1201
        try:
939
1202
            try:
945
1208
        finally:
946
1209
            source.unlock()
947
1210
 
948
 
    def basis_tree(self):
949
 
        """Return `Tree` object for last revision.
950
 
 
951
 
        If there are no revisions yet, return an `EmptyTree`.
952
 
        """
953
 
        return self.revision_tree(self.last_revision())
954
 
 
955
1211
    @needs_write_lock
956
1212
    def rename_one(self, from_rel, to_rel):
957
 
        """Rename one file.
958
 
 
959
 
        This can change the directory or the filename or both.
960
 
        """
 
1213
        """See Branch.rename_one."""
961
1214
        tree = self.working_tree()
962
1215
        inv = tree.inventory
963
1216
        if not tree.has_filename(from_rel):
999
1252
 
1000
1253
    @needs_write_lock
1001
1254
    def move(self, from_paths, to_name):
1002
 
        """Rename files.
1003
 
 
1004
 
        to_name must exist as a versioned directory.
1005
 
 
1006
 
        If to_name exists and is a directory, the files are moved into
1007
 
        it, keeping their old names.  If it is a directory, 
1008
 
 
1009
 
        Note that to_name is only the last component of the new name;
1010
 
        this doesn't change the directory.
1011
 
 
1012
 
        This returns a list of (from_path, to_path) pairs for each
1013
 
        entry that is moved.
1014
 
        """
 
1255
        """See Branch.move."""
1015
1256
        result = []
1016
1257
        ## TODO: Option to move IDs only
1017
1258
        assert not isinstance(from_paths, basestring)
1063
1304
        return result
1064
1305
 
1065
1306
    def get_parent(self):
1066
 
        """Return the parent location of the branch.
1067
 
 
1068
 
        This is the default location for push/pull/missing.  The usual
1069
 
        pattern is that the user can override it by specifying a
1070
 
        location.
1071
 
        """
 
1307
        """See Branch.get_parent."""
1072
1308
        import errno
1073
1309
        _locs = ['parent', 'pull', 'x-pull']
1074
1310
        for l in _locs:
1080
1316
        return None
1081
1317
 
1082
1318
    def get_push_location(self):
1083
 
        """Return the None or the location to push this branch to."""
 
1319
        """See Branch.get_push_location."""
1084
1320
        config = bzrlib.config.BranchConfig(self)
1085
1321
        push_loc = config.get_user_option('push_location')
1086
1322
        return push_loc
1087
1323
 
1088
1324
    def set_push_location(self, location):
1089
 
        """Set a new push location for this branch."""
 
1325
        """See Branch.set_push_location."""
1090
1326
        config = bzrlib.config.LocationConfig(self.base)
1091
1327
        config.set_user_option('push_location', location)
1092
1328
 
1093
1329
    @needs_write_lock
1094
1330
    def set_parent(self, url):
 
1331
        """See Branch.set_parent."""
1095
1332
        # TODO: Maybe delete old location files?
1096
1333
        from bzrlib.atomicfile import AtomicFile
1097
1334
        f = AtomicFile(self.controlfilename('parent'))
1101
1338
        finally:
1102
1339
            f.close()
1103
1340
 
 
1341
    def tree_config(self):
 
1342
        return TreeConfig(self)
 
1343
 
1104
1344
    def check_revno(self, revno):
1105
1345
        """\
1106
1346
        Check whether a revno corresponds to any revision.
1118
1358
            raise InvalidRevisionNumber(revno)
1119
1359
        
1120
1360
    def sign_revision(self, revision_id, gpg_strategy):
 
1361
        """See Branch.sign_revision."""
1121
1362
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1122
1363
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1123
1364
 
1124
1365
    @needs_write_lock
1125
1366
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1367
        """See Branch.store_revision_signature."""
1126
1368
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1127
1369
                                revision_id, "sig")
1128
1370
 
1210
1452
            other.unlock()
1211
1453
 
1212
1454
 
1213
 
class ScratchBranch(_Branch):
 
1455
class ScratchBranch(BzrBranch):
1214
1456
    """Special test class: a branch that cleans up after itself.
1215
1457
 
1216
1458
    >>> b = ScratchBranch()