/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

Add some basic documentation in 'bzr help git'.

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,
135
143
class ContentCache(object):
136
144
    """Object that can cache Git objects."""
137
145
 
 
146
    def add(self, object):
 
147
        """Add an object."""
 
148
        raise NotImplementedError(self.add)
 
149
 
 
150
    def add_multi(self, objects):
 
151
        """Add multiple objects."""
 
152
        for obj in objects:
 
153
            self.add(obj)
 
154
 
138
155
    def __getitem__(self, sha):
139
156
        """Retrieve an item, by SHA."""
140
157
        raise NotImplementedError(self.__getitem__)
141
158
 
142
159
 
143
160
class BzrGitCacheFormat(object):
 
161
    """Bazaar-Git Cache Format."""
144
162
 
145
163
    def get_format_string(self):
146
164
        """Return a single-line unique format string for this cache format."""
151
169
        raise NotImplementedError(self.open)
152
170
 
153
171
    def initialize(self, transport):
 
172
        """Create a new instance of this cache format at transport."""
154
173
        transport.put_bytes('format', self.get_format_string())
155
174
 
156
175
    @classmethod
195
214
 
196
215
 
197
216
class CacheUpdater(object):
 
217
    """Base class for objects that can update a bzr-git cache."""
198
218
 
199
 
    def add_object(self, obj, ie):
 
219
    def add_object(self, obj, ie, path):
200
220
        raise NotImplementedError(self.add_object)
201
221
 
202
222
    def finish(self):
212
232
        self._cache_updater_klass = cache_updater_klass
213
233
 
214
234
    def get_updater(self, rev):
 
235
        """Update an object that implements the CacheUpdater interface for 
 
236
        updating this cache.
 
237
        """
215
238
        return self._cache_updater_klass(self, rev)
216
239
 
217
240
 
219
242
 
220
243
 
221
244
class DictCacheUpdater(CacheUpdater):
 
245
    """Cache updater for dict-based caches."""
222
246
 
223
247
    def __init__(self, cache, rev):
224
248
        self.cache = cache
227
251
        self._commit = None
228
252
        self._entries = []
229
253
 
230
 
    def add_object(self, obj, ie):
 
254
    def add_object(self, obj, ie, path):
231
255
        if obj.type_name == "commit":
232
256
            self._commit = obj
233
257
            assert ie is None
253
277
 
254
278
 
255
279
class DictGitShaMap(GitShaMap):
 
280
    """Git SHA map that uses a dictionary."""
256
281
 
257
282
    def __init__(self):
258
283
        self._by_sha = {}
290
315
        self._trees = []
291
316
        self._blobs = []
292
317
 
293
 
    def add_object(self, obj, ie):
 
318
    def add_object(self, obj, ie, path):
294
319
        if obj.type_name == "commit":
295
320
            self._commit = obj
296
321
            assert ie is None
335
360
 
336
361
 
337
362
class SqliteGitShaMap(GitShaMap):
 
363
    """Bazaar GIT Sha map that uses a sqlite database for storage."""
338
364
 
339
365
    def __init__(self, path=None):
340
366
        self.path = path
373
399
        return "%s(%r)" % (self.__class__.__name__, self.path)
374
400
    
375
401
    def lookup_commit(self, revid):
376
 
        row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
 
402
        cursor = self.db.execute("select sha1 from commits where revid = ?", 
 
403
            (revid,))
 
404
        row = cursor.fetchone()
377
405
        if row is not None:
378
406
            return row[0]
379
407
        raise KeyError
423
451
 
424
452
 
425
453
class TdbCacheUpdater(CacheUpdater):
 
454
    """Cache updater for tdb-based caches."""
426
455
 
427
456
    def __init__(self, cache, rev):
428
457
        self.cache = cache
432
461
        self._commit = None
433
462
        self._entries = []
434
463
 
435
 
    def add_object(self, obj, ie):
 
464
    def add_object(self, obj, ie, path):
436
465
        sha = obj.sha().digest()
437
466
        if obj.type_name == "commit":
438
467
            self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
461
490
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
462
491
 
463
492
class TdbGitCacheFormat(BzrGitCacheFormat):
 
493
    """Cache format for tdb-based caches."""
464
494
 
465
495
    def get_format_string(self):
466
496
        return 'bzr-git sha map version 3 using tdb\n'
564
594
                yield sha_to_hex(key[4:])
565
595
 
566
596
 
 
597
class VersionedFilesContentCache(ContentCache):
 
598
 
 
599
    def __init__(self, vf):
 
600
        self._vf = vf
 
601
 
 
602
    def add(self, obj):
 
603
        self._vf.insert_record_stream(
 
604
            [versionedfile.ChunkedContentFactory((obj.id,), [], None,
 
605
                obj.as_legacy_object_chunks())])
 
606
 
 
607
    def __getitem__(self, sha):
 
608
        stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
 
609
        entry = stream.next() 
 
610
        if entry.storage_kind == 'absent':
 
611
            raise KeyError(sha)
 
612
        return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
 
613
 
 
614
 
 
615
class GitObjectStoreContentCache(ContentCache):
 
616
 
 
617
    def __init__(self, store):
 
618
        self.store = store
 
619
 
 
620
    def add_multi(self, objs):
 
621
        self.store.add_objects(objs)
 
622
 
 
623
    def add(self, obj, path):
 
624
        self.store.add_object(obj)
 
625
 
 
626
    def __getitem__(self, sha):
 
627
        return self.store[sha]
 
628
 
 
629
 
 
630
class IndexCacheUpdater(CacheUpdater):
 
631
 
 
632
    def __init__(self, cache, rev):
 
633
        self.cache = cache
 
634
        self.revid = rev.revision_id
 
635
        self.parent_revids = rev.parent_ids
 
636
        self._commit = None
 
637
        self._entries = []
 
638
        self._cache_objs = set()
 
639
 
 
640
    def add_object(self, obj, ie, path):
 
641
        if obj.type_name == "commit":
 
642
            self._commit = obj
 
643
            assert ie is None
 
644
            self.cache.idmap._add_git_sha(obj.id, "commit",
 
645
                (self.revid, obj.tree))
 
646
            self.cache.idmap._add_node(("commit", self.revid, "X"),
 
647
                " ".join((obj.id, obj.tree)))
 
648
            self._cache_objs.add((obj, path))
 
649
        elif obj.type_name == "blob":
 
650
            self.cache.idmap._add_git_sha(obj.id, "blob",
 
651
                (ie.file_id, ie.revision))
 
652
            self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
 
653
            if ie.kind == "symlink":
 
654
                self._cache_objs.add((obj, path))
 
655
        elif obj.type_name == "tree":
 
656
            self.cache.idmap._add_git_sha(obj.id, "tree",
 
657
                (ie.file_id, self.revid))
 
658
            self._cache_objs.add((obj, path))
 
659
        else:
 
660
            raise AssertionError
 
661
 
 
662
    def finish(self):
 
663
        self.cache.content_cache.add_multi(self._cache_objs)
 
664
        return self._commit
 
665
 
 
666
 
 
667
class IndexBzrGitCache(BzrGitCache):
 
668
 
 
669
    def __init__(self, transport=None):
 
670
        mapper = versionedfile.ConstantMapper("trees")
 
671
        shamap = IndexGitShaMap(transport.clone('index'))
 
672
        #trees_store = knit.make_file_factory(True, mapper)(transport)
 
673
        #content_cache = VersionedFilesContentCache(trees_store)
 
674
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
675
        store = TransportObjectStore(transport.clone('objects'))
 
676
        content_cache = GitObjectStoreContentCache(store)
 
677
        super(IndexBzrGitCache, self).__init__(shamap, content_cache,
 
678
                IndexCacheUpdater)
 
679
 
 
680
 
 
681
class IndexGitCacheFormat(BzrGitCacheFormat):
 
682
 
 
683
    def get_format_string(self):
 
684
        return 'bzr-git sha map with git object cache version 1\n'
 
685
 
 
686
    def initialize(self, transport):
 
687
        super(IndexGitCacheFormat, self).initialize(transport)
 
688
        transport.mkdir('index')
 
689
        transport.mkdir('objects')
 
690
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
691
        TransportObjectStore.init(transport.clone('objects'))
 
692
 
 
693
    def open(self, transport):
 
694
        return IndexBzrGitCache(transport)
 
695
 
 
696
 
 
697
class IndexGitShaMap(GitShaMap):
 
698
    """SHA Map that uses the Bazaar APIs to store a cache.
 
699
 
 
700
    BTree Index file with the following contents:
 
701
 
 
702
    ("git", <sha1>) -> "<type> <type-data1> <type-data2>"
 
703
    ("commit", <revid>) -> "<sha1> <tree-id>"
 
704
    ("blob", <fileid>, <revid>) -> <sha1>
 
705
 
 
706
    """
 
707
 
 
708
    def __init__(self, transport=None):
 
709
        if transport is None:
 
710
            self._transport = None
 
711
            self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
 
712
            self._builder = self._index
 
713
        else:
 
714
            self._builder = None
 
715
            self._transport = transport
 
716
            self._index = _mod_index.CombinedGraphIndex([])
 
717
            for name in self._transport.list_dir("."):
 
718
                if not name.endswith(".rix"):
 
719
                    continue
 
720
                x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
 
721
                    self._transport.stat(name).st_size)
 
722
                self._index.insert_index(0, x)
 
723
 
 
724
    @classmethod
 
725
    def from_repository(cls, repository):
 
726
        transport = getattr(repository, "_transport", None)
 
727
        if transport is not None:
 
728
            try:
 
729
                transport.mkdir('git')
 
730
            except bzrlib.errors.FileExists:
 
731
                pass
 
732
            return cls(transport.clone('git'))
 
733
        from bzrlib.transport import get_transport
 
734
        return cls(get_transport(get_cache_dir()))
 
735
 
 
736
    def __repr__(self):
 
737
        if self._transport is not None:
 
738
            return "%s(%r)" % (self.__class__.__name__, self._transport.base)
 
739
        else:
 
740
            return "%s()" % (self.__class__.__name__)
 
741
 
 
742
    def repack(self):
 
743
        assert self._builder is None
 
744
        self.start_write_group()
 
745
        for _, key, value in self._index.iter_all_entries():
 
746
            self._builder.add_node(key, value)
 
747
        to_remove = []
 
748
        for name in self._transport.list_dir('.'):
 
749
            if name.endswith('.rix'):
 
750
                to_remove.append(name)
 
751
        self.commit_write_group()
 
752
        del self._index.indices[1:]
 
753
        for name in to_remove:
 
754
            self._transport.rename(name, name + '.old')
 
755
 
 
756
    def start_write_group(self):
 
757
        assert self._builder is None
 
758
        self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
 
759
        self._name = osutils.sha()
 
760
 
 
761
    def commit_write_group(self):
 
762
        assert self._builder is not None
 
763
        stream = self._builder.finish()
 
764
        name = self._name.hexdigest() + ".rix"
 
765
        size = self._transport.put_file(name, stream)
 
766
        index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
 
767
        self._index.insert_index(0, index)
 
768
        self._builder = None
 
769
        self._name = None
 
770
 
 
771
    def abort_write_group(self):
 
772
        assert self._builder is not None
 
773
        self._builder = None
 
774
        self._name = None
 
775
 
 
776
    def _add_node(self, key, value):
 
777
        try:
 
778
            self._builder.add_node(key, value)
 
779
        except bzrlib.errors.BadIndexDuplicateKey:
 
780
            # Multiple bzr objects can have the same contents
 
781
            return True
 
782
        else:
 
783
            return False
 
784
 
 
785
    def _get_entry(self, key):
 
786
        entries = self._index.iter_entries([key])
 
787
        try:
 
788
            return entries.next()[2]
 
789
        except StopIteration:
 
790
            if self._builder is None:
 
791
                raise KeyError
 
792
            entries = self._builder.iter_entries([key])
 
793
            try:
 
794
                return entries.next()[2]
 
795
            except StopIteration:
 
796
                raise KeyError
 
797
 
 
798
    def _iter_keys_prefix(self, prefix):
 
799
        for entry in self._index.iter_entries_prefix([prefix]):
 
800
            yield entry[1]
 
801
        if self._builder is not None:
 
802
            for entry in self._builder.iter_entries_prefix([prefix]):
 
803
                yield entry[1]
 
804
 
 
805
    def lookup_commit(self, revid):
 
806
        return self._get_entry(("commit", revid, "X"))[:40]
 
807
 
 
808
    def _add_git_sha(self, hexsha, type, type_data):
 
809
        if hexsha is not None:
 
810
            self._name.update(hexsha)
 
811
            self._add_node(("git", hexsha, "X"),
 
812
                " ".join((type, type_data[0], type_data[1])))
 
813
        else:
 
814
            # This object is not represented in Git - perhaps an empty
 
815
            # directory?
 
816
            self._name.update(type + " ".join(type_data))
 
817
 
 
818
    def lookup_blob_id(self, fileid, revision):
 
819
        return self._get_entry(("blob", fileid, revision))
 
820
 
 
821
    def lookup_git_sha(self, sha):
 
822
        if len(sha) == 20:
 
823
            sha = sha_to_hex(sha)
 
824
        data = self._get_entry(("git", sha, "X")).split(" ", 2)
 
825
        return (data[0], (data[1], data[2]))
 
826
 
 
827
    def revids(self):
 
828
        """List the revision ids known."""
 
829
        for key in self._iter_keys_prefix(("commit", None, None)):
 
830
            yield key[1]
 
831
 
 
832
    def missing_revisions(self, revids):
 
833
        """Return set of all the revisions that are not present."""
 
834
        missing_revids = set(revids)
 
835
        for _, key, value in self._index.iter_entries((
 
836
            ("commit", revid, "X") for revid in revids)):
 
837
            missing_revids.remove(key[1])
 
838
        return missing_revids
 
839
 
 
840
    def sha1s(self):
 
841
        """List the SHA1s."""
 
842
        for key in self._iter_keys_prefix(("git", None, None)):
 
843
            yield key[1]
 
844
 
 
845
 
567
846
formats = registry.Registry()
568
847
formats.register(TdbGitCacheFormat().get_format_string(),
569
848
    TdbGitCacheFormat())
570
849
formats.register(SqliteGitCacheFormat().get_format_string(),
571
850
    SqliteGitCacheFormat())
 
851
formats.register(IndexGitCacheFormat().get_format_string(),
 
852
    IndexGitCacheFormat())
 
853
# In the future, this will become the default:
 
854
# formats.register('default', IndexGitCacheFormat())
572
855
try:
573
856
    import tdb
574
857
except ImportError:
577
860
    formats.register('default', TdbGitCacheFormat())
578
861
 
579
862
 
 
863
 
580
864
def migrate_ancient_formats(repo_transport):
581
865
    # Prefer migrating git.db over git.tdb, since the latter may not 
582
866
    # be openable on some platforms.