/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Refactored out ControlFiles and RevisionStore from _Branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
37
37
                           UnlistableBranch, NoSuchFile, NotVersionedError,
38
38
                           NoWorkingTree)
39
39
from bzrlib.textui import show_status
40
 
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
41
 
                             NULL_REVISION)
 
40
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
42
41
 
43
42
from bzrlib.delta import compare_trees
44
43
from bzrlib.tree import EmptyTree, RevisionTree
45
44
from bzrlib.inventory import Inventory
46
45
from bzrlib.store import copy_all
47
 
from bzrlib.store.compressed_text import CompressedTextStore
48
 
from bzrlib.store.text import TextStore
49
 
from bzrlib.store.weave import WeaveStore
50
 
from bzrlib.testament import Testament
51
46
import bzrlib.transactions as transactions
52
47
from bzrlib.transport import Transport, get_transport
53
48
import bzrlib.xml5
54
49
import bzrlib.ui
55
50
from config import TreeConfig
 
51
from control_files import ControlFiles
 
52
from rev_storage import RevisionStorage
56
53
 
57
54
 
58
55
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
167
164
    nick = property(_get_nick, _set_nick)
168
165
        
169
166
 
170
 
 
171
 
class _Branch(Branch):
 
167
class _Branch(Branch, ControlFiles):
172
168
    """A branch stored in the actual filesystem.
173
169
 
174
170
    Note that it's "local" in the context of the filesystem; it doesn't
175
171
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
176
172
    it's writable, and can be accessed via the normal filesystem API.
177
173
 
178
 
    _lock_mode
179
 
        None, or 'r' or 'w'
180
 
 
181
 
    _lock_count
182
 
        If _lock_mode is true, a positive count of the number of times the
183
 
        lock has been taken.
184
 
 
185
 
    _lock
186
 
        Lock object from bzrlib.lock.
187
174
    """
188
175
    # We actually expect this class to be somewhat short-lived; part of its
189
176
    # purpose is to try to isolate what bits of the branch logic are tied to
190
177
    # filesystem access, so that in a later step, we can extricate them to
191
178
    # a separarte ("storage") class.
192
 
    _lock_mode = None
193
 
    _lock_count = None
194
 
    _lock = None
195
179
    _inventory_weave = None
196
180
    
197
181
    # Map some sort of prefix into a namespace
239
223
        """
240
224
        assert isinstance(transport, Transport), \
241
225
            "%r is not a Transport" % transport
242
 
        self._transport = transport
 
226
        ControlFiles.__init__(self, transport, 'branch-lock')
243
227
        if init:
244
228
            self._make_control()
245
229
        self._check_format(relax_version_check)
246
 
 
247
 
        def get_store(name, compressed=True, prefixed=False):
248
 
            # FIXME: This approach of assuming stores are all entirely compressed
249
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
250
 
            # some existing branches where there's a mixture; we probably 
251
 
            # still want the option to look for both.
252
 
            relpath = self._rel_controlfilename(name)
253
 
            if compressed:
254
 
                store = CompressedTextStore(self._transport.clone(relpath),
255
 
                                            prefixed=prefixed)
256
 
            else:
257
 
                store = TextStore(self._transport.clone(relpath),
258
 
                                  prefixed=prefixed)
259
 
            #if self._transport.should_cache():
260
 
            #    cache_path = os.path.join(self.cache_root, name)
261
 
            #    os.mkdir(cache_path)
262
 
            #    store = bzrlib.store.CachedStore(store, cache_path)
263
 
            return store
264
 
        def get_weave(name, prefixed=False):
265
 
            relpath = self._rel_controlfilename(name)
266
 
            ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
267
 
            if self._transport.should_cache():
268
 
                ws.enable_cache = True
269
 
            return ws
270
 
 
271
 
        if self._branch_format == 4:
272
 
            self.inventory_store = get_store('inventory-store')
273
 
            self.text_store = get_store('text-store')
274
 
            self.revision_store = get_store('revision-store')
275
 
        elif self._branch_format == 5:
276
 
            self.control_weaves = get_weave('')
277
 
            self.weave_store = get_weave('weaves')
278
 
            self.revision_store = get_store('revision-store', compressed=False)
279
 
        elif self._branch_format == 6:
280
 
            self.control_weaves = get_weave('')
281
 
            self.weave_store = get_weave('weaves', prefixed=True)
282
 
            self.revision_store = get_store('revision-store', compressed=False,
283
 
                                            prefixed=True)
284
 
        self.revision_store.register_suffix('sig')
285
 
        self._transaction = None
 
230
        self.storage = RevisionStorage(transport, self._branch_format)
286
231
 
287
232
    def __str__(self):
288
233
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
290
235
    __repr__ = __str__
291
236
 
292
237
    def __del__(self):
293
 
        if self._lock_mode or self._lock:
294
 
            # XXX: This should show something every time, and be suitable for
295
 
            # headless operation and embedding
296
 
            warn("branch %r was not explicitly unlocked" % self)
297
 
            self._lock.unlock()
298
 
 
299
238
        # TODO: It might be best to do this somewhere else,
300
239
        # but it is nice for a Branch object to automatically
301
240
        # cache it's information.
316
255
 
317
256
    base = property(_get_base, doc="The URL for the root of this branch.")
318
257
 
319
 
    def _finish_transaction(self):
320
 
        """Exit the current transaction."""
321
 
        if self._transaction is None:
322
 
            raise errors.LockError('Branch %s is not in a transaction' %
323
 
                                   self)
324
 
        transaction = self._transaction
325
 
        self._transaction = None
326
 
        transaction.finish()
327
 
 
328
 
    def get_transaction(self):
329
 
        """Return the current active transaction.
330
 
 
331
 
        If no transaction is active, this returns a passthrough object
332
 
        for which all data is immediately flushed and no caching happens.
333
 
        """
334
 
        if self._transaction is None:
335
 
            return transactions.PassThroughTransaction()
336
 
        else:
337
 
            return self._transaction
338
 
 
339
 
    def _set_transaction(self, new_transaction):
340
 
        """Set a new active transaction."""
341
 
        if self._transaction is not None:
342
 
            raise errors.LockError('Branch %s is in a transaction already.' %
343
 
                                   self)
344
 
        self._transaction = new_transaction
345
 
 
346
 
    def lock_write(self):
347
 
        mutter("lock write: %s (%s)", self, self._lock_count)
348
 
        # TODO: Upgrade locking to support using a Transport,
349
 
        # and potentially a remote locking protocol
350
 
        if self._lock_mode:
351
 
            if self._lock_mode != 'w':
352
 
                raise LockError("can't upgrade to a write lock from %r" %
353
 
                                self._lock_mode)
354
 
            self._lock_count += 1
355
 
        else:
356
 
            self._lock = self._transport.lock_write(
357
 
                    self._rel_controlfilename('branch-lock'))
358
 
            self._lock_mode = 'w'
359
 
            self._lock_count = 1
360
 
            self._set_transaction(transactions.PassThroughTransaction())
361
 
 
362
 
    def lock_read(self):
363
 
        mutter("lock read: %s (%s)", self, self._lock_count)
364
 
        if self._lock_mode:
365
 
            assert self._lock_mode in ('r', 'w'), \
366
 
                   "invalid lock mode %r" % self._lock_mode
367
 
            self._lock_count += 1
368
 
        else:
369
 
            self._lock = self._transport.lock_read(
370
 
                    self._rel_controlfilename('branch-lock'))
371
 
            self._lock_mode = 'r'
372
 
            self._lock_count = 1
373
 
            self._set_transaction(transactions.ReadOnlyTransaction())
374
 
            # 5K may be excessive, but hey, its a knob.
375
 
            self.get_transaction().set_cache_size(5000)
376
 
                        
377
 
    def unlock(self):
378
 
        mutter("unlock: %s (%s)", self, self._lock_count)
379
 
        if not self._lock_mode:
380
 
            raise LockError('branch %r is not locked' % (self))
381
 
 
382
 
        if self._lock_count > 1:
383
 
            self._lock_count -= 1
384
 
        else:
385
 
            self._finish_transaction()
386
 
            self._lock.unlock()
387
 
            self._lock = None
388
 
            self._lock_mode = self._lock_count = None
389
 
 
390
258
    def abspath(self, name):
391
259
        """Return absolute filename for something in the branch
392
260
        
395
263
        """
396
264
        return self._transport.abspath(name)
397
265
 
398
 
    def _rel_controlfilename(self, file_or_path):
399
 
        if not isinstance(file_or_path, basestring):
400
 
            file_or_path = '/'.join(file_or_path)
401
 
        if file_or_path == '':
402
 
            return bzrlib.BZRDIR
403
 
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
404
 
 
405
 
    def controlfilename(self, file_or_path):
406
 
        """Return location relative to branch."""
407
 
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
408
 
 
409
 
    def controlfile(self, file_or_path, mode='r'):
410
 
        """Open a control file for this branch.
411
 
 
412
 
        There are two classes of file in the control directory: text
413
 
        and binary.  binary files are untranslated byte streams.  Text
414
 
        control files are stored with Unix newlines and in UTF-8, even
415
 
        if the platform or locale defaults are different.
416
 
 
417
 
        Controlfiles should almost never be opened in write mode but
418
 
        rather should be atomically copied and replaced using atomicfile.
419
 
        """
420
 
        import codecs
421
 
 
422
 
        relpath = self._rel_controlfilename(file_or_path)
423
 
        #TODO: codecs.open() buffers linewise, so it was overloaded with
424
 
        # a much larger buffer, do we need to do the same for getreader/getwriter?
425
 
        if mode == 'rb': 
426
 
            return self._transport.get(relpath)
427
 
        elif mode == 'wb':
428
 
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
429
 
        elif mode == 'r':
430
 
            # XXX: Do we really want errors='replace'?   Perhaps it should be
431
 
            # an error, or at least reported, if there's incorrectly-encoded
432
 
            # data inside a file.
433
 
            # <https://launchpad.net/products/bzr/+bug/3823>
434
 
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
435
 
        elif mode == 'w':
436
 
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
437
 
        else:
438
 
            raise BzrError("invalid controlfile mode %r" % mode)
439
 
 
440
 
    def put_controlfile(self, path, f, encode=True):
441
 
        """Write an entry as a controlfile.
442
 
 
443
 
        :param path: The path to put the file, relative to the .bzr control
444
 
                     directory
445
 
        :param f: A file-like or string object whose contents should be copied.
446
 
        :param encode:  If true, encode the contents as utf-8
447
 
        """
448
 
        self.put_controlfiles([(path, f)], encode=encode)
449
 
 
450
 
    def put_controlfiles(self, files, encode=True):
451
 
        """Write several entries as controlfiles.
452
 
 
453
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
454
 
                      underneath the bzr control directory
455
 
        :param encode:  If true, encode the contents as utf-8
456
 
        """
457
 
        import codecs
458
 
        ctrl_files = []
459
 
        for path, f in files:
460
 
            if encode:
461
 
                if isinstance(f, basestring):
462
 
                    f = f.encode('utf-8', 'replace')
463
 
                else:
464
 
                    f = codecs.getwriter('utf-8')(f, errors='replace')
465
 
            path = self._rel_controlfilename(path)
466
 
            ctrl_files.append((path, f))
467
 
        self._transport.put_multi(ctrl_files)
468
 
 
469
266
    def _make_control(self):
470
267
        from bzrlib.inventory import Inventory
471
268
        from bzrlib.weavefile import write_weave_v5
531
328
 
532
329
    def get_root_id(self):
533
330
        """Return the id of this branches root"""
534
 
        inv = self.get_inventory(self.last_revision())
 
331
        inv = self.storage.get_inventory(self.last_revision())
535
332
        return inv.root.file_id
536
333
 
 
334
    def write_lock(self):
 
335
        ControlFiles.write_lock(self)
 
336
        self.storage.write_lock()
 
337
 
 
338
    def read_lock(self):
 
339
        ControlFiles.read_lock(self)
 
340
        self.storage.read_lock()
 
341
 
 
342
    def unlock(self):
 
343
        try:
 
344
            self.storage.unlock()
 
345
        except:
 
346
            pass
 
347
        ControlFiles.unlock(self)
 
348
 
537
349
    @needs_write_lock
538
350
    def set_root_id(self, file_id):
539
351
        inv = self.working_tree().read_working_inventory()
670
482
    def set_revision_history(self, rev_history):
671
483
        self.put_controlfile('revision-history', '\n'.join(rev_history))
672
484
 
673
 
    def has_revision(self, revision_id):
674
 
        """True if this branch has a copy of the revision.
675
 
 
676
 
        This does not necessarily imply the revision is merge
677
 
        or on the mainline."""
678
 
        return (revision_id is None
679
 
                or self.revision_store.has_id(revision_id))
680
 
 
681
 
    @needs_read_lock
682
 
    def get_revision_xml_file(self, revision_id):
683
 
        """Return XML file object for revision object."""
684
 
        if not revision_id or not isinstance(revision_id, basestring):
685
 
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
686
 
        try:
687
 
            return self.revision_store.get(revision_id)
688
 
        except (IndexError, KeyError):
689
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
690
 
 
691
 
    #deprecated
692
 
    get_revision_xml = get_revision_xml_file
693
 
 
694
 
    def get_revision_xml(self, revision_id):
695
 
        return self.get_revision_xml_file(revision_id).read()
696
 
 
697
 
 
698
 
    def get_revision(self, revision_id):
699
 
        """Return the Revision object for a named revision"""
700
 
        xml_file = self.get_revision_xml_file(revision_id)
701
 
 
702
 
        try:
703
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
704
 
        except SyntaxError, e:
705
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
706
 
                                         [revision_id,
707
 
                                          str(e)])
708
 
            
709
 
        assert r.revision_id == revision_id
710
 
        return r
711
 
 
712
485
    def get_revision_delta(self, revno):
713
486
        """Return the delta for one revision.
714
487
 
722
495
 
723
496
        # revno is 1-based; list is 0-based
724
497
 
725
 
        new_tree = self.revision_tree(rh[revno-1])
 
498
        new_tree = self.storage.revision_tree(rh[revno-1])
726
499
        if revno == 1:
727
500
            old_tree = EmptyTree()
728
501
        else:
729
 
            old_tree = self.revision_tree(rh[revno-2])
 
502
            old_tree = self.storage.revision_tree(rh[revno-2])
730
503
 
731
504
        return compare_trees(old_tree, new_tree)
732
505
 
733
 
    def get_revision_sha1(self, revision_id):
734
 
        """Hash the stored value of a revision, and return it."""
735
 
        # In the future, revision entries will be signed. At that
736
 
        # point, it is probably best *not* to include the signature
737
 
        # in the revision hash. Because that lets you re-sign
738
 
        # the revision, (add signatures/remove signatures) and still
739
 
        # have all hash pointers stay consistent.
740
 
        # But for now, just hash the contents.
741
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
742
 
 
743
506
    def get_ancestry(self, revision_id):
744
507
        """Return a list of revision-ids integrated by a revision.
745
508
        
748
511
        """
749
512
        if revision_id is None:
750
513
            return [None]
751
 
        w = self.get_inventory_weave()
 
514
        w = self.storage.get_inventory_weave()
752
515
        return [None] + map(w.idx_to_name,
753
516
                            w.inclusions([w.lookup(revision_id)]))
754
517
 
755
 
    def get_inventory_weave(self):
756
 
        return self.control_weaves.get_weave('inventory',
757
 
                                             self.get_transaction())
758
 
 
759
 
    def get_inventory(self, revision_id):
760
 
        """Get Inventory object by hash."""
761
 
        xml = self.get_inventory_xml(revision_id)
762
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
763
 
 
764
 
    def get_inventory_xml(self, revision_id):
765
 
        """Get inventory XML as a file object."""
766
 
        try:
767
 
            assert isinstance(revision_id, basestring), type(revision_id)
768
 
            iw = self.get_inventory_weave()
769
 
            return iw.get_text(iw.lookup(revision_id))
770
 
        except IndexError:
771
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
772
 
 
773
 
    def get_inventory_sha1(self, revision_id):
774
 
        """Return the sha1 hash of the inventory entry
775
 
        """
776
 
        return self.get_revision(revision_id).inventory_sha1
777
 
 
778
 
    def get_revision_inventory(self, revision_id):
779
 
        """Return inventory of a past revision."""
780
 
        # TODO: Unify this with get_inventory()
781
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
782
 
        # must be the same as its revision, so this is trivial.
783
 
        if revision_id == None:
784
 
            # This does not make sense: if there is no revision,
785
 
            # then it is the current tree inventory surely ?!
786
 
            # and thus get_root_id() is something that looks at the last
787
 
            # commit on the branch, and the get_root_id is an inventory check.
788
 
            raise NotImplementedError
789
 
            # return Inventory(self.get_root_id())
790
 
        else:
791
 
            return self.get_inventory(revision_id)
792
 
 
793
518
    @needs_read_lock
794
519
    def revision_history(self):
795
520
        """Return sequence of revision hashes on to this branch."""
890
615
        except DivergedBranches, e:
891
616
            try:
892
617
                pullable_revs = get_intervening_revisions(self.last_revision(),
893
 
                                                          stop_revision, self)
 
618
                                                          stop_revision, 
 
619
                                                          self.storage)
894
620
                assert self.last_revision() not in pullable_revs
895
621
                return pullable_revs
896
622
            except bzrlib.errors.NotAncestor:
923
649
            raise bzrlib.errors.NoSuchRevision(self, revno)
924
650
        return history[revno - 1]
925
651
 
926
 
    def revision_tree(self, revision_id):
927
 
        """Return Tree for a revision on this branch.
928
 
 
929
 
        `revision_id` may be None for the null revision, in which case
930
 
        an `EmptyTree` is returned."""
931
 
        # TODO: refactor this to use an existing revision object
932
 
        # so we don't need to read it in twice.
933
 
        if revision_id == None or revision_id == NULL_REVISION:
934
 
            return EmptyTree()
935
 
        else:
936
 
            inv = self.get_revision_inventory(revision_id)
937
 
            return RevisionTree(self.weave_store, inv, revision_id)
938
 
 
939
652
    def working_tree(self):
940
653
        """Return a `Tree` for the working copy."""
941
654
        from bzrlib.workingtree import WorkingTree
966
679
 
967
680
        If there are no revisions yet, return an `EmptyTree`.
968
681
        """
969
 
        return self.revision_tree(self.last_revision())
 
682
        return self.storage.revision_tree(self.last_revision())
970
683
 
971
684
    @needs_write_lock
972
685
    def rename_one(self, from_rel, to_rel):
1212
925
        if revno < 1 or revno > self.revno():
1213
926
            raise InvalidRevisionNumber(revno)
1214
927
        
1215
 
    def sign_revision(self, revision_id, gpg_strategy):
1216
 
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1217
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1218
 
 
1219
 
    @needs_write_lock
1220
 
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1221
 
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1222
 
                                revision_id, "sig")
1223
 
 
1224
928
 
1225
929
 
1226
930
class ScratchBranch(_Branch):