~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

first cut at merge from integration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
import bzrlib
30
30
from bzrlib.config import TreeConfig
 
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
32
from bzrlib.delta import compare_trees
32
33
import bzrlib.errors as errors
33
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
39
40
                           NoWorkingTree)
40
41
import bzrlib.inventory as inventory
41
42
from bzrlib.inventory import Inventory
 
43
from bzrlib.lockable_files import LockableFiles
42
44
from bzrlib.osutils import (isdir, quotefn,
43
45
                            rename, splitpath, sha_file,
44
46
                            file_kind, abspath, normpath, pathjoin,
47
49
from bzrlib.textui import show_status
48
50
from bzrlib.trace import mutter, note
49
51
from bzrlib.tree import EmptyTree, RevisionTree
50
 
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
51
 
                             NULL_REVISION)
 
52
from bzrlib.repository import Repository
 
53
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
52
54
from bzrlib.store import copy_all
53
 
from bzrlib.store.text import TextStore
54
 
from bzrlib.store.weave import WeaveStore
55
55
from bzrlib.symbol_versioning import *
56
 
from bzrlib.testament import Testament
57
56
import bzrlib.transactions as transactions
58
57
from bzrlib.transport import Transport, get_transport
 
58
from bzrlib.tree import EmptyTree, RevisionTree
 
59
import bzrlib.ui
59
60
import bzrlib.xml5
60
 
import bzrlib.ui
61
61
 
62
62
 
63
63
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
64
64
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
65
65
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
66
 
## TODO: Maybe include checks for common corruption of newlines, etc?
67
 
 
 
66
 
 
67
 
 
68
# TODO: Maybe include checks for common corruption of newlines, etc?
68
69
 
69
70
# TODO: Some operations like log might retrieve the same revisions
70
71
# repeatedly to calculate deltas.  We could perhaps have a weakref
71
72
# cache in memory to make this faster.  In general anything can be
72
 
# cached in memory between lock and unlock operations.
73
 
 
74
 
def find_branch(*ignored, **ignored_too):
75
 
    # XXX: leave this here for about one release, then remove it
76
 
    raise NotImplementedError('find_branch() is not supported anymore, '
77
 
                              'please use one of the new branch constructors')
78
 
 
79
 
 
80
 
def needs_read_lock(unbound):
81
 
    """Decorate unbound to take out and release a read lock."""
82
 
    def decorated(self, *args, **kwargs):
83
 
        self.lock_read()
84
 
        try:
85
 
            return unbound(self, *args, **kwargs)
86
 
        finally:
87
 
            self.unlock()
88
 
    return decorated
89
 
 
90
 
 
91
 
def needs_write_lock(unbound):
92
 
    """Decorate unbound to take out and release a write lock."""
93
 
    def decorated(self, *args, **kwargs):
94
 
        self.lock_write()
95
 
        try:
96
 
            return unbound(self, *args, **kwargs)
97
 
        finally:
98
 
            self.unlock()
99
 
    return decorated
 
73
# cached in memory between lock and unlock operations. .. nb thats
 
74
# what the transaction identity map provides
 
75
 
100
76
 
101
77
######################################################################
102
78
# branch objects
199
175
        """Subclasses that care about caching should override this, and set
200
176
        up cached stores located under cache_root.
201
177
        """
 
178
        # seems to be unused, 2006-01-13 mbp
 
179
        warn('%s is deprecated' % self.setup_caching)
202
180
        self.cache_root = cache_root
203
181
 
204
182
    def _get_nick(self):
216
194
        """Copy the content of this branches store to branch_to."""
217
195
        raise NotImplementedError('push_stores is abstract')
218
196
 
219
 
    def get_transaction(self):
220
 
        """Return the current active transaction.
221
 
 
222
 
        If no transaction is active, this returns a passthrough object
223
 
        for which all data is immediately flushed and no caching happens.
224
 
        """
225
 
        raise NotImplementedError('get_transaction is abstract')
226
 
 
227
197
    def lock_write(self):
228
198
        raise NotImplementedError('lock_write is abstract')
229
199
        
233
203
    def unlock(self):
234
204
        raise NotImplementedError('unlock is abstract')
235
205
 
 
206
    def peek_lock_mode(self):
 
207
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
208
        raise NotImplementedError(self.peek_lock_mode)
 
209
 
236
210
    def abspath(self, name):
237
211
        """Return absolute filename for something in the branch
238
212
        
241
215
        """
242
216
        raise NotImplementedError('abspath is abstract')
243
217
 
244
 
    def controlfilename(self, file_or_path):
245
 
        """Return location relative to branch."""
246
 
        raise NotImplementedError('controlfilename is abstract')
247
 
 
248
 
    def controlfile(self, file_or_path, mode='r'):
249
 
        """Open a control file for this branch.
250
 
 
251
 
        There are two classes of file in the control directory: text
252
 
        and binary.  binary files are untranslated byte streams.  Text
253
 
        control files are stored with Unix newlines and in UTF-8, even
254
 
        if the platform or locale defaults are different.
255
 
 
256
 
        Controlfiles should almost never be opened in write mode but
257
 
        rather should be atomically copied and replaced using atomicfile.
258
 
        """
259
 
        raise NotImplementedError('controlfile is abstract')
260
 
 
261
 
    def put_controlfile(self, path, f, encode=True):
262
 
        """Write an entry as a controlfile.
263
 
 
264
 
        :param path: The path to put the file, relative to the .bzr control
265
 
                     directory
266
 
        :param f: A file-like or string object whose contents should be copied.
267
 
        :param encode:  If true, encode the contents as utf-8
268
 
        """
269
 
        raise NotImplementedError('put_controlfile is abstract')
270
 
 
271
 
    def put_controlfiles(self, files, encode=True):
272
 
        """Write several entries as controlfiles.
273
 
 
274
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
275
 
                      underneath the bzr control directory
276
 
        :param encode:  If true, encode the contents as utf-8
277
 
        """
278
 
        raise NotImplementedError('put_controlfiles is abstract')
279
 
 
280
218
    def get_root_id(self):
281
219
        """Return the id of this branches root"""
282
220
        raise NotImplementedError('get_root_id is abstract')
283
221
 
284
 
    def set_root_id(self, file_id):
285
 
        raise NotImplementedError('set_root_id is abstract')
286
 
 
287
222
    def print_file(self, file, revision_id):
288
223
        """Print `file` to stdout."""
289
224
        raise NotImplementedError('print_file is abstract')
294
229
    def set_revision_history(self, rev_history):
295
230
        raise NotImplementedError('set_revision_history is abstract')
296
231
 
297
 
    def has_revision(self, revision_id):
298
 
        """True if this branch has a copy of the revision.
299
 
 
300
 
        This does not necessarily imply the revision is merge
301
 
        or on the mainline."""
302
 
        raise NotImplementedError('has_revision 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
232
    def revision_history(self):
361
233
        """Return sequence of revision hashes on to this branch."""
362
234
        raise NotImplementedError('revision_history is abstract')
420
292
            if stop_revision > other_len:
421
293
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
422
294
        return other_history[self_len:stop_revision]
423
 
    
 
295
 
424
296
    def update_revisions(self, other, stop_revision=None):
425
297
        """Pull in new perfect-fit revisions."""
426
298
        raise NotImplementedError('update_revisions is abstract')
448
320
            raise bzrlib.errors.NoSuchRevision(self, revno)
449
321
        return history[revno - 1]
450
322
 
451
 
    def revision_tree(self, revision_id):
452
 
        """Return Tree for a revision on this branch.
453
 
 
454
 
        `revision_id` may be None for the null revision, in which case
455
 
        an `EmptyTree` is returned."""
456
 
        raise NotImplementedError('revision_tree is abstract')
457
 
 
458
323
    def working_tree(self):
459
324
        """Return a `Tree` for the working copy if this is a local branch."""
460
325
        raise NotImplementedError('working_tree is abstract')
467
332
 
468
333
        If there are no revisions yet, return an `EmptyTree`.
469
334
        """
470
 
        return self.revision_tree(self.last_revision())
 
335
        return self.repository.revision_tree(self.last_revision())
471
336
 
472
337
    def rename_one(self, from_rel, to_rel):
473
338
        """Rename one file.
534
399
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
535
400
        raise NotImplementedError('store_revision_signature is abstract')
536
401
 
 
402
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
403
        """Copy this branch into the existing directory to_location.
 
404
 
 
405
        Returns the newly created branch object.
 
406
 
 
407
        revision
 
408
            If not None, only revisions up to this point will be copied.
 
409
            The head of the new branch will be that revision.  Must be a
 
410
            revid or None.
 
411
    
 
412
        to_location -- The destination directory; must either exist and be 
 
413
            empty, or not exist, in which case it is created.
 
414
    
 
415
        basis_branch
 
416
            A local branch to copy revisions from, related to this branch. 
 
417
            This is used when branching from a remote (slow) branch, and we have
 
418
            a local branch that might contain some relevant revisions.
 
419
    
 
420
        to_branch_type
 
421
            Branch type of destination branch
 
422
        """
 
423
        from bzrlib.workingtree import WorkingTree
 
424
        assert isinstance(to_location, basestring)
 
425
        if not bzrlib.osutils.lexists(to_location):
 
426
            os.mkdir(to_location)
 
427
        if to_branch_type is None:
 
428
            to_branch_type = BzrBranch
 
429
        print "FIXME use a branch format here"
 
430
        br_to = to_branch_type.initialize(to_location)
 
431
        mutter("copy branch from %s to %s", self, br_to)
 
432
        if basis_branch is not None:
 
433
            basis_branch.push_stores(br_to)
 
434
        if revision is None:
 
435
            revision = self.last_revision()
 
436
        br_to.update_revisions(self, stop_revision=revision)
 
437
        br_to.set_parent(self.base)
 
438
        WorkingTree.create(br_to, to_location).set_root_id(self.get_root_id())
 
439
        mutter("copied")
 
440
        return br_to
 
441
 
537
442
    def fileid_involved_between_revs(self, from_revid, to_revid):
538
443
        """ This function returns the file_id(s) involved in the
539
444
            changes between the from_revid revision and the to_revid
648
553
 
649
554
        # Since we don't have a .bzr directory, inherit the
650
555
        # mode from the root directory
651
 
        dir_mode, file_mode = self._find_modes(t)
652
 
 
653
 
        t.mkdir('.bzr', mode=dir_mode)
 
556
        temp_control = LockableFiles(t, '')
 
557
        temp_control._transport.mkdir('.bzr',
 
558
                                      mode=temp_control._dir_mode)
 
559
        file_mode = temp_control._file_mode
 
560
        del temp_control
 
561
        mutter('created control directory in ' + t.base)
654
562
        control = t.clone('.bzr')
655
563
        dirs = ['revision-store', 'weaves']
656
 
        files = [('README', 
657
 
            StringIO("This is a Bazaar-NG control directory.\n"
658
 
            "Do not change any files in this directory.\n")),
659
 
            ('branch-format', StringIO(self.get_format_string())),
660
 
            ('revision-history', StringIO('')),
661
 
            ('branch-name', StringIO('')),
662
 
            ('branch-lock', StringIO('')),
663
 
            ('inventory.weave', StringIO(empty_weave)),
664
 
        ]
665
 
        control.mkdir_multi(dirs, mode=dir_mode)
666
 
        control.put_multi(files, mode=file_mode)
667
 
        mutter('created control directory in ' + t.base)
668
 
        return BzrBranch(t, format=self)
 
564
        lock_file = 'branch-lock'
 
565
        utf8_files = [('README', 
 
566
                       "This is a Bazaar-NG control directory.\n"
 
567
                       "Do not change any files in this directory.\n"),
 
568
                      ('branch-format', self.get_format_string()),
 
569
                      ('revision-history', ''),
 
570
                      ('branch-name', ''),
 
571
                      ]
 
572
        files = [('inventory.weave', StringIO(empty_weave)), 
 
573
                 ]
 
574
        
 
575
        # FIXME: RBC 20060125 dont peek under the covers
 
576
        # NB: no need to escape relative paths that are url safe.
 
577
        control.put(lock_file, StringIO(), mode=file_mode)
 
578
        control_files = LockableFiles(control, lock_file)
 
579
        control_files.lock_write()
 
580
        control_files._transport.mkdir_multi(dirs,
 
581
                mode=control_files._dir_mode)
 
582
        try:
 
583
            for file, content in utf8_files:
 
584
                control_files.put_utf8(file, content)
 
585
            for file, content in files:
 
586
                control_files.put(file, content)
 
587
        finally:
 
588
            control_files.unlock()
 
589
        return BzrBranch(t, _format=self, _control_files=control_files)
669
590
 
670
591
    def is_supported(self):
671
592
        """Is this format supported?
678
599
 
679
600
    def open(self, transport):
680
601
        """Fill out the data in branch for the branch at url."""
681
 
        return BzrBranch(transport, format=self)
 
602
        return BzrBranch(transport, _format=self)
682
603
 
683
604
    @classmethod
684
605
    def register_format(klass, format):
763
684
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
764
685
    it's writable, and can be accessed via the normal filesystem API.
765
686
 
766
 
    _lock_mode
767
 
        None, or 'r' or 'w'
768
 
 
769
 
    _lock_count
770
 
        If _lock_mode is true, a positive count of the number of times the
771
 
        lock has been taken.
772
 
 
773
 
    _lock
774
 
        Lock object from bzrlib.lock.
775
687
    """
776
688
    # We actually expect this class to be somewhat short-lived; part of its
777
689
    # purpose is to try to isolate what bits of the branch logic are tied to
778
690
    # filesystem access, so that in a later step, we can extricate them to
779
691
    # a separarte ("storage") class.
780
 
    _lock_mode = None
781
 
    _lock_count = None
782
 
    _lock = None
783
692
    _inventory_weave = None
784
 
    # If set to False (by a plugin, etc) BzrBranch will not set the
785
 
    # mode on created files or directories
786
 
    _set_file_mode = True
787
 
    _set_dir_mode = True
788
693
    
789
694
    # Map some sort of prefix into a namespace
790
695
    # stuff like "revno:10", "revid:", etc.
813
718
            raise UnlistableBranch(from_store)
814
719
 
815
720
    def __init__(self, transport, init=deprecated_nonce,
816
 
                 relax_version_check=deprecated_nonce, format=None):
 
721
                 relax_version_check=deprecated_nonce, _format=None,
 
722
                 _control_files=None):
817
723
        """Create new branch object at a particular location.
818
724
 
819
725
        transport -- A Transport object, defining how to access files.
833
739
        assert isinstance(transport, Transport), \
834
740
            "%r is not a Transport" % transport
835
741
        self._transport = transport
 
742
        self._base = self._transport.base
 
743
        if _control_files is None:
 
744
            _control_files = LockableFiles(self._transport.clone(bzrlib.BZRDIR),
 
745
                                           'branch-lock')
 
746
        self.control_files = _control_files
836
747
        if deprecated_passed(init):
837
748
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
838
749
                 "deprecated as of bzr 0.8. Please use Branch.create().",
842
753
                # this is slower than before deprecation, oh well never mind.
843
754
                # -> its deprecated.
844
755
                self._initialize(transport.base)
845
 
        self._find_modes()
846
 
        self._check_format(format)
 
756
        self._check_format(_format)
847
757
        if deprecated_passed(relax_version_check):
848
758
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
849
759
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
858
768
                        ['use a different bzr version',
859
769
                         'or remove the .bzr directory'
860
770
                         ' and "bzr init" again'])
861
 
 
862
 
        def get_store(name, compressed=True, prefixed=False):
863
 
            relpath = self._rel_controlfilename(safe_unicode(name))
864
 
            store = TextStore(self._transport.clone(relpath),
865
 
                              dir_mode=self._dir_mode,
866
 
                              file_mode=self._file_mode,
867
 
                              prefixed=prefixed,
868
 
                              compressed=compressed)
869
 
            return store
870
 
 
871
 
        def get_weave(name, prefixed=False):
872
 
            relpath = self._rel_controlfilename(unicode(name))
873
 
            ws = WeaveStore(self._transport.clone(relpath),
874
 
                            prefixed=prefixed,
875
 
                            dir_mode=self._dir_mode,
876
 
                            file_mode=self._file_mode)
877
 
            if self._transport.should_cache():
878
 
                ws.enable_cache = True
879
 
            return ws
880
 
 
881
 
        if isinstance(self._branch_format, BzrBranchFormat4):
882
 
            self.inventory_store = get_store('inventory-store')
883
 
            self.text_store = get_store('text-store')
884
 
            self.revision_store = get_store('revision-store')
885
 
        elif isinstance(self._branch_format, BzrBranchFormat5):
886
 
            self.control_weaves = get_weave(u'')
887
 
            self.weave_store = get_weave(u'weaves')
888
 
            self.revision_store = get_store(u'revision-store', compressed=False)
889
 
        elif isinstance(self._branch_format, BzrBranchFormat6):
890
 
            self.control_weaves = get_weave(u'')
891
 
            self.weave_store = get_weave(u'weaves', prefixed=True)
892
 
            self.revision_store = get_store(u'revision-store', compressed=False,
893
 
                                            prefixed=True)
894
 
        self.revision_store.register_suffix('sig')
895
 
        self._transaction = None
 
771
        self.repository = Repository(transport, self._branch_format)
 
772
 
896
773
 
897
774
    @staticmethod
898
775
    def _initialize(base):
900
777
        return BzrBranchFormat6().initialize(base)
901
778
 
902
779
    def __str__(self):
903
 
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
 
780
        return '%s(%r)' % (self.__class__.__name__, self.base)
904
781
 
905
782
    __repr__ = __str__
906
783
 
907
784
    def __del__(self):
908
 
        if self._lock_mode or self._lock:
909
 
            # XXX: This should show something every time, and be suitable for
910
 
            # headless operation and embedding
911
 
            warn("branch %r was not explicitly unlocked" % self)
912
 
            self._lock.unlock()
913
 
 
914
785
        # TODO: It might be best to do this somewhere else,
915
786
        # but it is nice for a Branch object to automatically
916
787
        # cache it's information.
917
788
        # Alternatively, we could have the Transport objects cache requests
918
789
        # See the earlier discussion about how major objects (like Branch)
919
790
        # should never expect their __del__ function to run.
 
791
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
920
792
        if hasattr(self, 'cache_root') and self.cache_root is not None:
921
793
            try:
922
794
                shutil.rmtree(self.cache_root)
925
797
            self.cache_root = None
926
798
 
927
799
    def _get_base(self):
928
 
        if self._transport:
929
 
            return self._transport.base
930
 
        return None
 
800
        return self._base
931
801
 
932
802
    base = property(_get_base, doc="The URL for the root of this branch.")
933
803
 
934
804
    def _finish_transaction(self):
935
805
        """Exit the current transaction."""
936
 
        if self._transaction is None:
937
 
            raise errors.LockError('Branch %s is not in a transaction' %
938
 
                                   self)
939
 
        transaction = self._transaction
940
 
        self._transaction = None
941
 
        transaction.finish()
 
806
        return self.control_files._finish_transaction()
942
807
 
943
808
    def get_transaction(self):
944
 
        """See Branch.get_transaction."""
945
 
        if self._transaction is None:
946
 
            return transactions.PassThroughTransaction()
947
 
        else:
948
 
            return self._transaction
949
 
 
950
 
    def _set_transaction(self, new_transaction):
 
809
        """Return the current active transaction.
 
810
 
 
811
        If no transaction is active, this returns a passthrough object
 
812
        for which all data is immediately flushed and no caching happens.
 
813
        """
 
814
        # this is an explicit function so that we can do tricky stuff
 
815
        # when the storage in rev_storage is elsewhere.
 
816
        # we probably need to hook the two 'lock a location' and 
 
817
        # 'have a transaction' together more delicately, so that
 
818
        # we can have two locks (branch and storage) and one transaction
 
819
        # ... and finishing the transaction unlocks both, but unlocking
 
820
        # does not. - RBC 20051121
 
821
        return self.control_files.get_transaction()
 
822
 
 
823
    def _set_transaction(self, transaction):
951
824
        """Set a new active transaction."""
952
 
        if self._transaction is not None:
953
 
            raise errors.LockError('Branch %s is in a transaction already.' %
954
 
                                   self)
955
 
        self._transaction = new_transaction
956
 
 
957
 
    def lock_write(self):
958
 
        #mutter("lock write: %s (%s)", self, self._lock_count)
959
 
        # TODO: Upgrade locking to support using a Transport,
960
 
        # and potentially a remote locking protocol
961
 
        if self._lock_mode:
962
 
            if self._lock_mode != 'w':
963
 
                raise LockError("can't upgrade to a write lock from %r" %
964
 
                                self._lock_mode)
965
 
            self._lock_count += 1
966
 
        else:
967
 
            self._lock = self._transport.lock_write(
968
 
                    self._rel_controlfilename('branch-lock'))
969
 
            self._lock_mode = 'w'
970
 
            self._lock_count = 1
971
 
            self._set_transaction(transactions.PassThroughTransaction())
972
 
 
973
 
    def lock_read(self):
974
 
        #mutter("lock read: %s (%s)", self, self._lock_count)
975
 
        if self._lock_mode:
976
 
            assert self._lock_mode in ('r', 'w'), \
977
 
                   "invalid lock mode %r" % self._lock_mode
978
 
            self._lock_count += 1
979
 
        else:
980
 
            self._lock = self._transport.lock_read(
981
 
                    self._rel_controlfilename('branch-lock'))
982
 
            self._lock_mode = 'r'
983
 
            self._lock_count = 1
984
 
            self._set_transaction(transactions.ReadOnlyTransaction())
985
 
            # 5K may be excessive, but hey, its a knob.
986
 
            self.get_transaction().set_cache_size(5000)
987
 
                        
988
 
    def unlock(self):
989
 
        #mutter("unlock: %s (%s)", self, self._lock_count)
990
 
        if not self._lock_mode:
991
 
            raise LockError('branch %r is not locked' % (self))
992
 
 
993
 
        if self._lock_count > 1:
994
 
            self._lock_count -= 1
995
 
        else:
996
 
            self._finish_transaction()
997
 
            self._lock.unlock()
998
 
            self._lock = None
999
 
            self._lock_mode = self._lock_count = None
 
825
        return self.control_files._set_transaction(transaction)
1000
826
 
1001
827
    def abspath(self, name):
1002
828
        """See Branch.abspath."""
1003
 
        return self._transport.abspath(name)
1004
 
 
1005
 
    def _rel_controlfilename(self, file_or_path):
1006
 
        if not isinstance(file_or_path, basestring):
1007
 
            file_or_path = u'/'.join(file_or_path)
1008
 
        if file_or_path == '':
1009
 
            return bzrlib.BZRDIR
1010
 
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
1011
 
 
1012
 
    def controlfilename(self, file_or_path):
1013
 
        """See Branch.controlfilename."""
1014
 
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
1015
 
 
1016
 
    def controlfile(self, file_or_path, mode='r'):
1017
 
        """See Branch.controlfile."""
1018
 
        import codecs
1019
 
 
1020
 
        relpath = self._rel_controlfilename(file_or_path)
1021
 
        #TODO: codecs.open() buffers linewise, so it was overloaded with
1022
 
        # a much larger buffer, do we need to do the same for getreader/getwriter?
1023
 
        if mode == 'rb': 
1024
 
            return self._transport.get(relpath)
1025
 
        elif mode == 'wb':
1026
 
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
1027
 
        elif mode == 'r':
1028
 
            # XXX: Do we really want errors='replace'?   Perhaps it should be
1029
 
            # an error, or at least reported, if there's incorrectly-encoded
1030
 
            # data inside a file.
1031
 
            # <https://launchpad.net/products/bzr/+bug/3823>
1032
 
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
1033
 
        elif mode == 'w':
1034
 
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
1035
 
        else:
1036
 
            raise BzrError("invalid controlfile mode %r" % mode)
1037
 
 
1038
 
    def put_controlfile(self, path, f, encode=True):
1039
 
        """See Branch.put_controlfile."""
1040
 
        self.put_controlfiles([(path, f)], encode=encode)
1041
 
 
1042
 
    def put_controlfiles(self, files, encode=True):
1043
 
        """See Branch.put_controlfiles."""
1044
 
        import codecs
1045
 
        ctrl_files = []
1046
 
        for path, f in files:
1047
 
            if encode:
1048
 
                if isinstance(f, basestring):
1049
 
                    f = StringIO(f.encode('utf-8', 'replace'))
1050
 
                else:
1051
 
                    f = codecs.getwriter('utf-8')(f, errors='replace')
1052
 
            path = self._rel_controlfilename(path)
1053
 
            ctrl_files.append((path, f))
1054
 
        self._transport.put_multi(ctrl_files, mode=self._file_mode)
1055
 
 
1056
 
    def _find_modes(self, path=None):
1057
 
        """Determine the appropriate modes for files and directories."""
1058
 
        try:
1059
 
            if path is None:
1060
 
                path = self._rel_controlfilename('')
1061
 
            st = self._transport.stat(path)
1062
 
        except errors.TransportNotPossible:
1063
 
            self._dir_mode = 0755
1064
 
            self._file_mode = 0644
1065
 
        else:
1066
 
            self._dir_mode = st.st_mode & 07777
1067
 
            # Remove the sticky and execute bits for files
1068
 
            self._file_mode = self._dir_mode & ~07111
1069
 
        if not self._set_dir_mode:
1070
 
            self._dir_mode = None
1071
 
        if not self._set_file_mode:
1072
 
            self._file_mode = None
 
829
        return self.control_files._transport.abspath(name)
1073
830
 
1074
831
    def _check_format(self, format):
1075
832
        """Identify the branch format if needed.
1088
845
    @needs_read_lock
1089
846
    def get_root_id(self):
1090
847
        """See Branch.get_root_id."""
1091
 
        inv = self.get_inventory(self.last_revision())
1092
 
        return inv.root.file_id
 
848
        tree = self.repository.revision_tree(self.last_revision())
 
849
        return tree.inventory.root.file_id
 
850
 
 
851
    def lock_write(self):
 
852
        # TODO: test for failed two phase locks. This is known broken.
 
853
        self.control_files.lock_write()
 
854
        self.repository.lock_write()
 
855
 
 
856
    def lock_read(self):
 
857
        # TODO: test for failed two phase locks. This is known broken.
 
858
        self.control_files.lock_read()
 
859
        self.repository.lock_read()
 
860
 
 
861
    def unlock(self):
 
862
        # TODO: test for failed two phase locks. This is known broken.
 
863
        self.repository.unlock()
 
864
        self.control_files.unlock()
 
865
 
 
866
    def peek_lock_mode(self):
 
867
        if self.control_files._lock_count == 0:
 
868
            return None
 
869
        else:
 
870
            return self.control_files._lock_mode
1093
871
 
1094
872
    @needs_read_lock
1095
873
    def print_file(self, file, revision_id):
1096
874
        """See Branch.print_file."""
1097
 
        tree = self.revision_tree(revision_id)
1098
 
        # use inventory as it was in that revision
1099
 
        file_id = tree.inventory.path2id(file)
1100
 
        if not file_id:
1101
 
            try:
1102
 
                revno = self.revision_id_to_revno(revision_id)
1103
 
            except errors.NoSuchRevision:
1104
 
                # TODO: This should not be BzrError,
1105
 
                # but NoSuchFile doesn't fit either
1106
 
                raise BzrError('%r is not present in revision %s' 
1107
 
                                % (file, revision_id))
1108
 
            else:
1109
 
                raise BzrError('%r is not present in revision %s'
1110
 
                                % (file, revno))
1111
 
        tree.print_file(file_id)
 
875
        return self.repository.print_file(file, revision_id)
1112
876
 
1113
877
    @needs_write_lock
1114
878
    def append_revision(self, *revision_ids):
1122
886
    @needs_write_lock
1123
887
    def set_revision_history(self, rev_history):
1124
888
        """See Branch.set_revision_history."""
1125
 
        old_revision = self.last_revision()
1126
 
        new_revision = rev_history[-1]
1127
 
        self.put_controlfile('revision-history', '\n'.join(rev_history))
1128
 
 
1129
 
    def has_revision(self, revision_id):
1130
 
        """See Branch.has_revision."""
1131
 
        return (revision_id is None
1132
 
                or self.revision_store.has_id(revision_id))
1133
 
 
1134
 
    @needs_read_lock
1135
 
    def _get_revision_xml_file(self, revision_id):
1136
 
        if not revision_id or not isinstance(revision_id, basestring):
1137
 
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
1138
 
        try:
1139
 
            return self.revision_store.get(revision_id)
1140
 
        except (IndexError, KeyError):
1141
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
1142
 
 
1143
 
    def get_revision_xml(self, revision_id):
1144
 
        """See Branch.get_revision_xml."""
1145
 
        return self._get_revision_xml_file(revision_id).read()
1146
 
 
1147
 
    def get_revision(self, revision_id):
1148
 
        """See Branch.get_revision."""
1149
 
        xml_file = self._get_revision_xml_file(revision_id)
1150
 
 
1151
 
        try:
1152
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1153
 
        except SyntaxError, e:
1154
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1155
 
                                         [revision_id,
1156
 
                                          str(e)])
1157
 
            
1158
 
        assert r.revision_id == revision_id
1159
 
        return r
1160
 
 
1161
 
    def get_revision_sha1(self, revision_id):
1162
 
        """See Branch.get_revision_sha1."""
1163
 
        # In the future, revision entries will be signed. At that
1164
 
        # point, it is probably best *not* to include the signature
1165
 
        # in the revision hash. Because that lets you re-sign
1166
 
        # the revision, (add signatures/remove signatures) and still
1167
 
        # have all hash pointers stay consistent.
1168
 
        # But for now, just hash the contents.
1169
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1170
 
 
1171
 
    def get_ancestry(self, revision_id):
1172
 
        """See Branch.get_ancestry."""
1173
 
        if revision_id is None:
1174
 
            return [None]
1175
 
        w = self._get_inventory_weave()
1176
 
        return [None] + map(w.idx_to_name,
1177
 
                            w.inclusions([w.lookup(revision_id)]))
1178
 
 
1179
 
    def _get_inventory_weave(self):
1180
 
        return self.control_weaves.get_weave('inventory',
1181
 
                                             self.get_transaction())
1182
 
 
1183
 
    def get_inventory(self, revision_id):
1184
 
        """See Branch.get_inventory."""
1185
 
        xml = self.get_inventory_xml(revision_id)
1186
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1187
 
 
1188
 
    def get_inventory_xml(self, revision_id):
1189
 
        """See Branch.get_inventory_xml."""
1190
 
        try:
1191
 
            assert isinstance(revision_id, basestring), type(revision_id)
1192
 
            iw = self._get_inventory_weave()
1193
 
            return iw.get_text(iw.lookup(revision_id))
1194
 
        except IndexError:
1195
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1196
 
 
1197
 
    def get_inventory_sha1(self, revision_id):
1198
 
        """See Branch.get_inventory_sha1."""
1199
 
        return self.get_revision(revision_id).inventory_sha1
1200
 
 
1201
 
    def get_revision_inventory(self, revision_id):
1202
 
        """See Branch.get_revision_inventory."""
1203
 
        # TODO: Unify this with get_inventory()
1204
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
1205
 
        # must be the same as its revision, so this is trivial.
1206
 
        if revision_id == None:
1207
 
            # This does not make sense: if there is no revision,
1208
 
            # then it is the current tree inventory surely ?!
1209
 
            # and thus get_root_id() is something that looks at the last
1210
 
            # commit on the branch, and the get_root_id is an inventory check.
1211
 
            raise NotImplementedError
1212
 
            # return Inventory(self.get_root_id())
 
889
        self.control_files.put_utf8(
 
890
            'revision-history', '\n'.join(rev_history))
 
891
 
 
892
    def get_revision_delta(self, revno):
 
893
        """Return the delta for one revision.
 
894
 
 
895
        The delta is relative to its mainline predecessor, or the
 
896
        empty tree for revision 1.
 
897
        """
 
898
        assert isinstance(revno, int)
 
899
        rh = self.revision_history()
 
900
        if not (1 <= revno <= len(rh)):
 
901
            raise InvalidRevisionNumber(revno)
 
902
 
 
903
        # revno is 1-based; list is 0-based
 
904
 
 
905
        new_tree = self.repository.revision_tree(rh[revno-1])
 
906
        if revno == 1:
 
907
            old_tree = EmptyTree()
1213
908
        else:
1214
 
            return self.get_inventory(revision_id)
 
909
            old_tree = self.repository.revision_tree(rh[revno-2])
 
910
        return compare_trees(old_tree, new_tree)
1215
911
 
1216
912
    @needs_read_lock
1217
913
    def revision_history(self):
1218
914
        """See Branch.revision_history."""
 
915
        # FIXME are transactions bound to control files ? RBC 20051121
1219
916
        transaction = self.get_transaction()
1220
917
        history = transaction.map.find_revision_history()
1221
918
        if history is not None:
1222
919
            mutter("cache hit for revision-history in %s", self)
1223
920
            return list(history)
1224
921
        history = [l.rstrip('\r\n') for l in
1225
 
                self.controlfile('revision-history', 'r').readlines()]
 
922
                self.control_files.get_utf8('revision-history').readlines()]
1226
923
        transaction.map.add_revision_history(history)
1227
924
        # this call is disabled because revision_history is 
1228
925
        # not really an object yet, and the transaction is for objects.
1252
949
        except DivergedBranches, e:
1253
950
            try:
1254
951
                pullable_revs = get_intervening_revisions(self.last_revision(),
1255
 
                                                          stop_revision, self)
 
952
                                                          stop_revision, 
 
953
                                                          self.repository)
1256
954
                assert self.last_revision() not in pullable_revs
1257
955
                return pullable_revs
1258
956
            except bzrlib.errors.NotAncestor:
1261
959
                else:
1262
960
                    raise e
1263
961
        
1264
 
    def revision_tree(self, revision_id):
1265
 
        """See Branch.revision_tree."""
1266
 
        # TODO: refactor this to use an existing revision object
1267
 
        # so we don't need to read it in twice.
1268
 
        if revision_id == None or revision_id == NULL_REVISION:
1269
 
            return EmptyTree()
1270
 
        else:
1271
 
            inv = self.get_revision_inventory(revision_id)
1272
 
            return RevisionTree(self, inv, revision_id)
1273
 
 
1274
962
    def basis_tree(self):
1275
963
        """See Branch.basis_tree."""
1276
964
        try:
1277
965
            revision_id = self.revision_history()[-1]
 
966
            # FIXME: This is an abstraction violation, the basis tree 
 
967
            # here as defined is on the working tree, the method should
 
968
            # be too. The basis tree for a branch can be different than
 
969
            # that for a working tree. RBC 20051207
1278
970
            xml = self.working_tree().read_basis_inventory(revision_id)
1279
971
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1280
 
            return RevisionTree(self, inv, revision_id)
 
972
            return RevisionTree(self.repository, inv, revision_id)
1281
973
        except (IndexError, NoSuchFile, NoWorkingTree), e:
1282
 
            return self.revision_tree(self.last_revision())
 
974
            return self.repository.revision_tree(self.last_revision())
1283
975
 
1284
976
    def working_tree(self):
1285
977
        """See Branch.working_tree."""
1286
978
        from bzrlib.workingtree import WorkingTree
1287
979
        from bzrlib.transport.local import LocalTransport
1288
 
        if (self._transport.base.find('://') != -1 or 
 
980
        if (self.base.find('://') != -1 or 
1289
981
            not isinstance(self._transport, LocalTransport)):
1290
982
            raise NoWorkingTree(self.base)
1291
983
        return WorkingTree(self.base, branch=self)
1314
1006
        _locs = ['parent', 'pull', 'x-pull']
1315
1007
        for l in _locs:
1316
1008
            try:
1317
 
                return self.controlfile(l, 'r').read().strip('\n')
 
1009
                return self.control_files.get_utf8(l).read().strip('\n')
1318
1010
            except NoSuchFile:
1319
1011
                pass
1320
1012
        return None
1334
1026
    def set_parent(self, url):
1335
1027
        """See Branch.set_parent."""
1336
1028
        # TODO: Maybe delete old location files?
1337
 
        from bzrlib.atomicfile import AtomicFile
1338
 
        f = AtomicFile(self.controlfilename('parent'))
1339
 
        try:
1340
 
            f.write(url + '\n')
1341
 
            f.commit()
1342
 
        finally:
1343
 
            f.close()
 
1029
        # URLs should never be unicode, even on the local fs,
 
1030
        # FIXUP this and get_parent in a future branch format bump:
 
1031
        # read and rewrite the file, and have the new format code read
 
1032
        # using .get not .get_utf8. RBC 20060125
 
1033
        self.control_files.put_utf8('parent', url + '\n')
1344
1034
 
1345
1035
    def tree_config(self):
1346
1036
        return TreeConfig(self)
1347
1037
 
1348
 
    def sign_revision(self, revision_id, gpg_strategy):
1349
 
        """See Branch.sign_revision."""
1350
 
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1351
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1352
 
 
1353
 
    @needs_write_lock
1354
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1355
 
        """See Branch.store_revision_signature."""
1356
 
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1357
 
                                revision_id, "sig")
 
1038
    def _get_truncated_history(self, revision_id):
 
1039
        history = self.revision_history()
 
1040
        if revision_id is None:
 
1041
            return history
 
1042
        try:
 
1043
            idx = history.index(revision_id)
 
1044
        except ValueError:
 
1045
            raise InvalidRevisionId(revision_id=revision, branch=self)
 
1046
        return history[:idx+1]
 
1047
 
 
1048
    @needs_read_lock
 
1049
    def _clone_weave(self, to_location, revision=None, basis_branch=None):
 
1050
        # prevent leakage
 
1051
        from bzrlib.workingtree import WorkingTree
 
1052
        assert isinstance(to_location, basestring)
 
1053
        if basis_branch is not None:
 
1054
            note("basis_branch is not supported for fast weave copy yet.")
 
1055
 
 
1056
        history = self._get_truncated_history(revision)
 
1057
        if not bzrlib.osutils.lexists(to_location):
 
1058
            os.mkdir(to_location)
 
1059
        branch_to = Branch.initialize(to_location)
 
1060
        mutter("copy branch from %s to %s", self, branch_to)
 
1061
 
 
1062
        self.repository.copy(branch_to.repository)
 
1063
        
 
1064
        # must be done *after* history is copied across
 
1065
        # FIXME duplicate code with base .clone().
 
1066
        # .. would template method be useful here?  RBC 20051207
 
1067
        branch_to.set_parent(self.base)
 
1068
        branch_to.append_revision(*history)
 
1069
        # FIXME: this should be in workingtree.clone
 
1070
        WorkingTree.create(branch_to, to_location).set_root_id(self.get_root_id())
 
1071
        mutter("copied")
 
1072
        return branch_to
 
1073
 
 
1074
    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
 
1075
        print "FIXME: clone via create and fetch is probably faster when versioned file comes in."
 
1076
        if to_branch_type is None:
 
1077
            to_branch_type = BzrBranch
 
1078
 
 
1079
        if to_branch_type == BzrBranch \
 
1080
            and self.repository.weave_store.listable() \
 
1081
            and self.repository.revision_store.listable():
 
1082
            return self._clone_weave(to_location, revision, basis_branch)
 
1083
 
 
1084
        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
1358
1085
 
1359
1086
    def fileid_involved_between_revs(self, from_revid, to_revid):
1360
1087
        """Find file_id(s) which are involved in the changes between revisions.
1372
1099
        #       If D is ever merged in the future, the weave
1373
1100
        #       won't be fixed, because AD never saw revision C
1374
1101
        #       to cause a conflict which would force a reweave.
1375
 
        w = self._get_inventory_weave()
 
1102
        w = self.repository.get_inventory_weave()
1376
1103
        from_set = set(w.inclusions([w.lookup(from_revid)]))
1377
1104
        to_set = set(w.inclusions([w.lookup(to_revid)]))
1378
1105
        included = to_set.difference(from_set)
1384
1111
 
1385
1112
        :param last_revid: If None, last_revision() will be used.
1386
1113
        """
1387
 
        w = self._get_inventory_weave()
 
1114
        w = self.repository.get_inventory_weave()
1388
1115
        if not last_revid:
1389
1116
            changed = set(w._names)
1390
1117
        else:
1401
1128
        #       or better yet, change _fileid_involved_by_set so
1402
1129
        #       that it takes the inventory weave, rather than
1403
1130
        #       pulling it out by itself.
1404
 
        w = self._get_inventory_weave()
 
1131
        w = self.repository.get_inventory_weave()
1405
1132
        return self._fileid_involved_by_set(changes)
1406
1133
 
1407
1134
    def _fileid_involved_by_set(self, changes):
1421
1148
                isinstance(self._branch_format, BzrBranchFormat6)), \
1422
1149
            "fileid_involved only supported for branches which store inventory as xml"
1423
1150
 
1424
 
        w = self._get_inventory_weave()
 
1151
        w = self.repository.get_inventory_weave()
1425
1152
        file_ids = set()
1426
1153
        for line in w._weave:
1427
1154
 
1509
1236
        else:
1510
1237
            super(ScratchBranch, self).__init__(transport)
1511
1238
 
 
1239
        # BzrBranch creates a clone to .bzr and then forgets about the
 
1240
        # original transport. A ScratchTransport() deletes itself and
 
1241
        # everything underneath it when it goes away, so we need to
 
1242
        # grab a local copy to prevent that from happening
 
1243
        self._transport = transport
 
1244
 
1512
1245
        for d in dirs:
1513
1246
            self._transport.mkdir(d)
1514
1247
            
1515
1248
        for f in files:
1516
1249
            self._transport.put(f, 'content of %s' % f)
1517
1250
 
1518
 
 
1519
1251
    def clone(self):
1520
1252
        """
1521
1253
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
1254
        >>> os.listdir(orig.base)
 
1255
        [u'.bzr', u'file1', u'file2']
1522
1256
        >>> clone = orig.clone()
1523
1257
        >>> if os.name != 'nt':
1524
1258
        ...   os.path.samefile(orig.base, clone.base)
1526
1260
        ...   orig.base == clone.base
1527
1261
        ...
1528
1262
        False
1529
 
        >>> os.path.isfile(pathjoin(clone.base, "file1"))
1530
 
        True
 
1263
        >>> os.listdir(clone.base)
 
1264
        [u'.bzr', u'file1', u'file2']
1531
1265
        """
1532
1266
        from shutil import copytree
1533
1267
        from bzrlib.osutils import mkdtemp