/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 cache.py

merge support for git object store-based caching mechanism.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
import os
24
24
import threading
25
25
 
 
26
from dulwich.objects import (
 
27
    ShaFile,
 
28
    )
 
29
 
26
30
import bzrlib
27
31
from bzrlib import (
 
32
    btree_index as _mod_btree_index,
 
33
    index as _mod_index,
 
34
    osutils,
28
35
    registry,
29
36
    trace,
 
37
    versionedfile,
30
38
    )
31
39
from bzrlib.transport import (
32
40
    get_transport,
141
149
 
142
150
 
143
151
class BzrGitCacheFormat(object):
 
152
    """Bazaar-Git Cache Format."""
144
153
 
145
154
    def get_format_string(self):
146
155
        """Return a single-line unique format string for this cache format."""
151
160
        raise NotImplementedError(self.open)
152
161
 
153
162
    def initialize(self, transport):
 
163
        """Create a new instance of this cache format at transport."""
154
164
        transport.put_bytes('format', self.get_format_string())
155
165
 
156
166
    @classmethod
195
205
 
196
206
 
197
207
class CacheUpdater(object):
 
208
    """Base class for objects that can update a bzr-git cache."""
198
209
 
199
210
    def add_object(self, obj, ie):
200
211
        raise NotImplementedError(self.add_object)
212
223
        self._cache_updater_klass = cache_updater_klass
213
224
 
214
225
    def get_updater(self, rev):
 
226
        """Update an object that implements the CacheUpdater interface for 
 
227
        updating this cache.
 
228
        """
215
229
        return self._cache_updater_klass(self, rev)
216
230
 
217
231
 
219
233
 
220
234
 
221
235
class DictCacheUpdater(CacheUpdater):
 
236
    """Cache updater for dict-based caches."""
222
237
 
223
238
    def __init__(self, cache, rev):
224
239
        self.cache = cache
253
268
 
254
269
 
255
270
class DictGitShaMap(GitShaMap):
 
271
    """Git SHA map that uses a dictionary."""
256
272
 
257
273
    def __init__(self):
258
274
        self._by_sha = {}
335
351
 
336
352
 
337
353
class SqliteGitShaMap(GitShaMap):
 
354
    """Bazaar GIT Sha map that uses a sqlite database for storage."""
338
355
 
339
356
    def __init__(self, path=None):
340
357
        self.path = path
373
390
        return "%s(%r)" % (self.__class__.__name__, self.path)
374
391
    
375
392
    def lookup_commit(self, revid):
376
 
        row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
 
393
        cursor = self.db.execute("select sha1 from commits where revid = ?", 
 
394
            (revid,))
 
395
        row = cursor.fetchone()
377
396
        if row is not None:
378
397
            return row[0]
379
398
        raise KeyError
423
442
 
424
443
 
425
444
class TdbCacheUpdater(CacheUpdater):
 
445
    """Cache updater for tdb-based caches."""
426
446
 
427
447
    def __init__(self, cache, rev):
428
448
        self.cache = cache
461
481
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
462
482
 
463
483
class TdbGitCacheFormat(BzrGitCacheFormat):
 
484
    """Cache format for tdb-based caches."""
464
485
 
465
486
    def get_format_string(self):
466
487
        return 'bzr-git sha map version 3 using tdb\n'
564
585
                yield sha_to_hex(key[4:])
565
586
 
566
587
 
 
588
class VersionedFilesContentCache(ContentCache):
 
589
 
 
590
    def __init__(self, vf):
 
591
        self._vf = vf
 
592
 
 
593
    def add(self, obj):
 
594
        self._vf.insert_record_stream(
 
595
            [versionedfile.ChunkedContentFactory((obj.id,), [], None,
 
596
                obj.as_legacy_object_chunks())])
 
597
 
 
598
    def __getitem__(self, sha):
 
599
        stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
 
600
        entry = stream.next() 
 
601
        if entry.storage_kind == 'absent':
 
602
            raise KeyError(sha)
 
603
        return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
 
604
 
 
605
 
 
606
class GitObjectStoreContentCache(ContentCache):
 
607
 
 
608
    def __init__(self, store):
 
609
        self.store = store
 
610
 
 
611
    def add(self, obj):
 
612
        self.store.add_object(obj)
 
613
 
 
614
    def __getitem__(self, sha):
 
615
        return self.store[sha]
 
616
 
 
617
 
 
618
class IndexCacheUpdater(CacheUpdater):
 
619
 
 
620
    def __init__(self, cache, rev):
 
621
        self.cache = cache
 
622
        self.revid = rev.revision_id
 
623
        self.parent_revids = rev.parent_ids
 
624
        self._commit = None
 
625
        self._entries = []
 
626
 
 
627
    def add_object(self, obj, ie):
 
628
        if obj.type_name == "commit":
 
629
            self._commit = obj
 
630
            assert ie is None
 
631
            self.cache.idmap._add_git_sha(obj.id, "commit",
 
632
                (self.revid, obj.tree))
 
633
            self.cache.idmap._add_node(("commit", self.revid, "X"),
 
634
                " ".join((obj.id, obj.tree)))
 
635
            self.cache.content_cache.add(obj)
 
636
        elif obj.type_name == "blob":
 
637
            self.cache.idmap._add_git_sha(obj.id, "blob",
 
638
                (ie.file_id, ie.revision))
 
639
            self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
 
640
            if ie.kind == "symlink":
 
641
                self.cache.content_cache.add(obj)
 
642
        elif obj.type_name == "tree":
 
643
            self.cache.idmap._add_git_sha(obj.id, "tree",
 
644
                (ie.file_id, self.revid))
 
645
            self.cache.content_cache.add(obj)
 
646
        else:
 
647
            raise AssertionError
 
648
 
 
649
    def finish(self):
 
650
        return self._commit
 
651
 
 
652
 
 
653
class IndexBzrGitCache(BzrGitCache):
 
654
 
 
655
    def __init__(self, transport=None):
 
656
        mapper = versionedfile.ConstantMapper("trees")
 
657
        shamap = IndexGitShaMap(transport.clone('index'))
 
658
        #trees_store = knit.make_file_factory(True, mapper)(transport)
 
659
        #content_cache = VersionedFilesContentCache(trees_store)
 
660
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
661
        store = TransportObjectStore(transport.clone('objects'))
 
662
        content_cache = GitObjectStoreContentCache(store)
 
663
        super(IndexBzrGitCache, self).__init__(shamap, content_cache,
 
664
                IndexCacheUpdater)
 
665
 
 
666
 
 
667
class IndexGitCacheFormat(BzrGitCacheFormat):
 
668
 
 
669
    def get_format_string(self):
 
670
        return 'bzr-git sha map with git object cache version 1\n'
 
671
 
 
672
    def initialize(self, transport):
 
673
        super(IndexGitCacheFormat, self).initialize(transport)
 
674
        transport.mkdir('index')
 
675
        transport.mkdir('objects')
 
676
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
677
        TransportObjectStore.init(transport.clone('objects'))
 
678
 
 
679
    def open(self, transport):
 
680
        return IndexBzrGitCache(transport)
 
681
 
 
682
 
 
683
class IndexGitShaMap(GitShaMap):
 
684
    """SHA Map that uses the Bazaar APIs to store a cache.
 
685
 
 
686
    BTree Index file with the following contents:
 
687
 
 
688
    ("git", <sha1>) -> "<type> <type-data1> <type-data2>"
 
689
    ("commit", <revid>) -> "<sha1> <tree-id>"
 
690
    ("blob", <fileid>, <revid>) -> <sha1>
 
691
 
 
692
    """
 
693
 
 
694
    def __init__(self, transport=None):
 
695
        if transport is None:
 
696
            self._transport = None
 
697
            self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
 
698
            self._builder = self._index
 
699
        else:
 
700
            self._builder = None
 
701
            self._transport = transport
 
702
            self._index = _mod_index.CombinedGraphIndex([])
 
703
            for name in self._transport.list_dir("."):
 
704
                if not name.endswith(".rix"):
 
705
                    continue
 
706
                x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
 
707
                    self._transport.stat(name).st_size)
 
708
                self._index.insert_index(0, x)
 
709
 
 
710
    @classmethod
 
711
    def from_repository(cls, repository):
 
712
        transport = getattr(repository, "_transport", None)
 
713
        if transport is not None:
 
714
            try:
 
715
                transport.mkdir('git')
 
716
            except bzrlib.errors.FileExists:
 
717
                pass
 
718
            return cls(transport.clone('git'))
 
719
        from bzrlib.transport import get_transport
 
720
        return cls(get_transport(get_cache_dir()))
 
721
 
 
722
    def __repr__(self):
 
723
        if self._transport is not None:
 
724
            return "%s(%r)" % (self.__class__.__name__, self._transport.base)
 
725
        else:
 
726
            return "%s()" % (self.__class__.__name__)
 
727
 
 
728
    def repack(self):
 
729
        assert self._builder is None
 
730
        self.start_write_group()
 
731
        for _, key, value in self._index.iter_all_entries():
 
732
            self._builder.add_node(key, value)
 
733
        to_remove = []
 
734
        for name in self._transport.list_dir('.'):
 
735
            if name.endswith('.rix'):
 
736
                to_remove.append(name)
 
737
        self.commit_write_group()
 
738
        del self._index.indices[1:]
 
739
        for name in to_remove:
 
740
            self._transport.rename(name, name + '.old')
 
741
 
 
742
    def start_write_group(self):
 
743
        assert self._builder is None
 
744
        self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
 
745
        self._name = osutils.sha()
 
746
 
 
747
    def commit_write_group(self):
 
748
        assert self._builder is not None
 
749
        stream = self._builder.finish()
 
750
        name = self._name.hexdigest() + ".rix"
 
751
        size = self._transport.put_file(name, stream)
 
752
        index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
 
753
        self._index.insert_index(0, index)
 
754
        self._builder = None
 
755
        self._name = None
 
756
 
 
757
    def abort_write_group(self):
 
758
        assert self._builder is not None
 
759
        self._builder = None
 
760
        self._name = None
 
761
 
 
762
    def _add_node(self, key, value):
 
763
        try:
 
764
            self._builder.add_node(key, value)
 
765
        except bzrlib.errors.BadIndexDuplicateKey:
 
766
            # Multiple bzr objects can have the same contents
 
767
            return True
 
768
        else:
 
769
            return False
 
770
 
 
771
    def _get_entry(self, key):
 
772
        entries = self._index.iter_entries([key])
 
773
        try:
 
774
            return entries.next()[2]
 
775
        except StopIteration:
 
776
            if self._builder is None:
 
777
                raise KeyError
 
778
            entries = self._builder.iter_entries([key])
 
779
            try:
 
780
                return entries.next()[2]
 
781
            except StopIteration:
 
782
                raise KeyError
 
783
 
 
784
    def _iter_keys_prefix(self, prefix):
 
785
        for entry in self._index.iter_entries_prefix([prefix]):
 
786
            yield entry[1]
 
787
        if self._builder is not None:
 
788
            for entry in self._builder.iter_entries_prefix([prefix]):
 
789
                yield entry[1]
 
790
 
 
791
    def lookup_commit(self, revid):
 
792
        return self._get_entry(("commit", revid, "X"))[:40]
 
793
 
 
794
    def _add_git_sha(self, hexsha, type, type_data):
 
795
        if hexsha is not None:
 
796
            self._name.update(hexsha)
 
797
            self._add_node(("git", hexsha, "X"),
 
798
                " ".join((type, type_data[0], type_data[1])))
 
799
        else:
 
800
            # This object is not represented in Git - perhaps an empty
 
801
            # directory?
 
802
            self._name.update(type + " ".join(type_data))
 
803
 
 
804
    def lookup_blob_id(self, fileid, revision):
 
805
        return self._get_entry(("blob", fileid, revision))
 
806
 
 
807
    def lookup_git_sha(self, sha):
 
808
        if len(sha) == 20:
 
809
            sha = sha_to_hex(sha)
 
810
        data = self._get_entry(("git", sha, "X")).split(" ", 2)
 
811
        return (data[0], (data[1], data[2]))
 
812
 
 
813
    def revids(self):
 
814
        """List the revision ids known."""
 
815
        for key in self._iter_keys_prefix(("commit", None, None)):
 
816
            yield key[1]
 
817
 
 
818
    def missing_revisions(self, revids):
 
819
        """Return set of all the revisions that are not present."""
 
820
        missing_revids = set(revids)
 
821
        for _, key, value in self._index.iter_entries((
 
822
            ("commit", revid, "X") for revid in revids)):
 
823
            missing_revids.remove(key[1])
 
824
        return missing_revids
 
825
 
 
826
    def sha1s(self):
 
827
        """List the SHA1s."""
 
828
        for key in self._iter_keys_prefix(("git", None, None)):
 
829
            yield key[1]
 
830
 
 
831
 
567
832
formats = registry.Registry()
568
833
formats.register(TdbGitCacheFormat().get_format_string(),
569
834
    TdbGitCacheFormat())
570
835
formats.register(SqliteGitCacheFormat().get_format_string(),
571
836
    SqliteGitCacheFormat())
 
837
formats.register(IndexGitCacheFormat().get_format_string(),
 
838
    IndexGitCacheFormat())
 
839
# In the future, this will become the default:
 
840
# formats.register('default', IndexGitCacheFormat())
572
841
try:
573
842
    import tdb
574
843
except ImportError:
577
846
    formats.register('default', TdbGitCacheFormat())
578
847
 
579
848
 
 
849
 
580
850
def migrate_ancient_formats(repo_transport):
581
851
    # Prefer migrating git.db over git.tdb, since the latter may not 
582
852
    # be openable on some platforms.