/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

More work on roundtrip push support.

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,
47
55
 
48
56
 
49
57
def get_remote_cache_transport():
 
58
    """Retrieve the transport to use when accessing (unwritable) remote 
 
59
    repositories.
 
60
    """
50
61
    return get_transport(get_cache_dir())
51
62
 
52
63
 
90
101
        """Lookup a Git sha in the database.
91
102
        :param sha: Git object sha
92
103
        :return: (type, type_data) with type_data:
93
 
            revision: revid, tree sha
 
104
            commit: revid, tree_sha, verifiers
 
105
            blob: fileid, revid
 
106
            tree: fileid, revid
94
107
        """
95
108
        raise NotImplementedError(self.lookup_git_sha)
96
109
 
135
148
class ContentCache(object):
136
149
    """Object that can cache Git objects."""
137
150
 
 
151
    def add(self, object):
 
152
        """Add an object."""
 
153
        raise NotImplementedError(self.add)
 
154
 
 
155
    def add_multi(self, objects):
 
156
        """Add multiple objects."""
 
157
        for obj in objects:
 
158
            self.add(obj)
 
159
 
138
160
    def __getitem__(self, sha):
139
161
        """Retrieve an item, by SHA."""
140
162
        raise NotImplementedError(self.__getitem__)
141
163
 
142
164
 
143
165
class BzrGitCacheFormat(object):
 
166
    """Bazaar-Git Cache Format."""
144
167
 
145
168
    def get_format_string(self):
146
169
        """Return a single-line unique format string for this cache format."""
151
174
        raise NotImplementedError(self.open)
152
175
 
153
176
    def initialize(self, transport):
 
177
        """Create a new instance of this cache format at transport."""
154
178
        transport.put_bytes('format', self.get_format_string())
155
179
 
156
180
    @classmethod
195
219
 
196
220
 
197
221
class CacheUpdater(object):
198
 
 
199
 
    def add_object(self, obj, ie):
 
222
    """Base class for objects that can update a bzr-git cache."""
 
223
 
 
224
    def add_object(self, obj, ie, path):
 
225
        """Add an object.
 
226
 
 
227
        :param obj: Object type ("commit", "blob" or "tree")
 
228
        :param ie: Inventory entry (for blob/tree) or testament_sha in case
 
229
            of commit
 
230
        :param path: Path of the object (optional)
 
231
        """
200
232
        raise NotImplementedError(self.add_object)
201
233
 
202
234
    def finish(self):
212
244
        self._cache_updater_klass = cache_updater_klass
213
245
 
214
246
    def get_updater(self, rev):
 
247
        """Update an object that implements the CacheUpdater interface for 
 
248
        updating this cache.
 
249
        """
215
250
        return self._cache_updater_klass(self, rev)
216
251
 
217
252
 
219
254
 
220
255
 
221
256
class DictCacheUpdater(CacheUpdater):
 
257
    """Cache updater for dict-based caches."""
222
258
 
223
259
    def __init__(self, cache, rev):
224
260
        self.cache = cache
227
263
        self._commit = None
228
264
        self._entries = []
229
265
 
230
 
    def add_object(self, obj, ie):
 
266
    def add_object(self, obj, ie, path):
231
267
        if obj.type_name == "commit":
232
268
            self._commit = obj
233
 
            assert ie is None
234
 
            type_data = (self.revid, self._commit.tree)
 
269
            assert type(ie) is dict
 
270
            type_data = (self.revid, self._commit.tree, ie)
235
271
            self.cache.idmap._by_revid[self.revid] = obj.id
236
272
        elif obj.type_name in ("blob", "tree"):
237
 
            if obj.type_name == "blob":
238
 
                revision = ie.revision
239
 
            else:
240
 
                revision = self.revid
241
 
            type_data = (ie.file_id, revision)
242
 
            self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
 
273
            if ie is not None:
 
274
                if obj.type_name == "blob":
 
275
                    revision = ie.revision
 
276
                else:
 
277
                    revision = self.revid
 
278
                type_data = (ie.file_id, revision)
 
279
                self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
243
280
        else:
244
281
            raise AssertionError
245
282
        self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
251
288
 
252
289
 
253
290
class DictGitShaMap(GitShaMap):
 
291
    """Git SHA map that uses a dictionary."""
254
292
 
255
293
    def __init__(self):
256
294
        self._by_sha = {}
288
326
        self._trees = []
289
327
        self._blobs = []
290
328
 
291
 
    def add_object(self, obj, ie):
 
329
    def add_object(self, obj, ie, path):
292
330
        if obj.type_name == "commit":
293
331
            self._commit = obj
294
 
            assert ie is None
 
332
            self._testament3_sha1 = ie["testament3-sha1"]
 
333
            assert type(ie) is dict
295
334
        elif obj.type_name == "tree":
296
 
            self._trees.append((obj.id, ie.file_id, self.revid))
 
335
            if ie is not None:
 
336
                self._trees.append((obj.id, ie.file_id, self.revid))
297
337
        elif obj.type_name == "blob":
298
 
            self._blobs.append((obj.id, ie.file_id, ie.revision))
 
338
            if ie is not None:
 
339
                self._blobs.append((obj.id, ie.file_id, ie.revision))
299
340
        else:
300
341
            raise AssertionError
301
342
 
309
350
            "replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
310
351
            self._blobs)
311
352
        self.db.execute(
312
 
            "replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
313
 
            (self._commit.id, self.revid, self._commit.tree))
 
353
            "replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
 
354
            (self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
314
355
        return self._commit
315
356
 
316
357
 
331
372
 
332
373
 
333
374
class SqliteGitShaMap(GitShaMap):
 
375
    """Bazaar GIT Sha map that uses a sqlite database for storage."""
334
376
 
335
377
    def __init__(self, path=None):
336
378
        self.path = path
364
406
        create unique index if not exists trees_sha1 on trees(sha1);
365
407
        create unique index if not exists trees_fileid_revid on trees(fileid, revid);
366
408
""")
 
409
        try:
 
410
            self.db.executescript(
 
411
                "ALTER TABLE commits ADD testament3_sha1 TEXT;")
 
412
        except sqlite3.OperationalError:
 
413
            pass # Column already exists.
367
414
 
368
415
    def __repr__(self):
369
416
        return "%s(%r)" % (self.__class__.__name__, self.path)
370
 
    
 
417
 
371
418
    def lookup_commit(self, revid):
372
 
        row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
 
419
        cursor = self.db.execute("select sha1 from commits where revid = ?", 
 
420
            (revid,))
 
421
        row = cursor.fetchone()
373
422
        if row is not None:
374
423
            return row[0]
375
424
        raise KeyError
394
443
 
395
444
        :param sha: Git object sha
396
445
        :return: (type, type_data) with type_data:
397
 
            revision: revid, tree sha
 
446
            commit: revid, tree sha, verifiers
 
447
            tree: fileid, revid
 
448
            blob: fileid, revid
398
449
        """
399
 
        row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
 
450
        row = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,)).fetchone()
400
451
        if row is not None:
401
 
            return ("commit", row)
 
452
            return ("commit", (row[0], row[1], {"testament3-sha1": row[2]}))
402
453
        row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
403
454
        if row is not None:
404
455
            return ("blob", row)
419
470
 
420
471
 
421
472
class TdbCacheUpdater(CacheUpdater):
 
473
    """Cache updater for tdb-based caches."""
422
474
 
423
475
    def __init__(self, cache, rev):
424
476
        self.cache = cache
428
480
        self._commit = None
429
481
        self._entries = []
430
482
 
431
 
    def add_object(self, obj, ie):
 
483
    def add_object(self, obj, ie, path):
432
484
        sha = obj.sha().digest()
433
485
        if obj.type_name == "commit":
434
486
            self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
435
 
            type_data = (self.revid, obj.tree)
 
487
            assert type(ie) is dict, "was %r" % ie
 
488
            type_data = (self.revid, obj.tree, ie["testament3-sha1"])
436
489
            self._commit = obj
437
 
            assert ie is None
438
490
        elif obj.type_name == "blob":
 
491
            if ie is None:
 
492
                return
439
493
            self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
440
494
            type_data = (ie.file_id, ie.revision)
441
495
        elif obj.type_name == "tree":
 
496
            if ie is None:
 
497
                return
442
498
            type_data = (ie.file_id, self.revid)
443
499
        else:
444
500
            raise AssertionError
453
509
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
454
510
 
455
511
class TdbGitCacheFormat(BzrGitCacheFormat):
 
512
    """Cache format for tdb-based caches."""
456
513
 
457
514
    def get_format_string(self):
458
515
        return 'bzr-git sha map version 3 using tdb\n'
523
580
 
524
581
    def lookup_blob_id(self, fileid, revision):
525
582
        return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
526
 
                
 
583
 
527
584
    def lookup_git_sha(self, sha):
528
585
        """Lookup a Git sha in the database.
529
586
 
530
587
        :param sha: Git object sha
531
588
        :return: (type, type_data) with type_data:
532
 
            revision: revid, tree sha
 
589
            commit: revid, tree sha
 
590
            blob: fileid, revid
 
591
            tree: fileid, revid
533
592
        """
534
593
        if len(sha) == 40:
535
594
            sha = hex_to_sha(sha)
536
595
        data = self.db["git\0" + sha].split("\0")
537
 
        return (data[0], (data[1], data[2]))
 
596
        if data[0] == "commit":
 
597
            if len(data) == 3:
 
598
                return (data[0], (data[1], data[2], {}))
 
599
            else:
 
600
                return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
 
601
        else:
 
602
            return (data[0], tuple(data[1:]))
538
603
 
539
604
    def missing_revisions(self, revids):
540
605
        ret = set()
556
621
                yield sha_to_hex(key[4:])
557
622
 
558
623
 
 
624
class VersionedFilesContentCache(ContentCache):
 
625
 
 
626
    def __init__(self, vf):
 
627
        self._vf = vf
 
628
 
 
629
    def add(self, obj):
 
630
        self._vf.insert_record_stream(
 
631
            [versionedfile.ChunkedContentFactory((obj.id,), [], None,
 
632
                obj.as_legacy_object_chunks())])
 
633
 
 
634
    def __getitem__(self, sha):
 
635
        stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
 
636
        entry = stream.next() 
 
637
        if entry.storage_kind == 'absent':
 
638
            raise KeyError(sha)
 
639
        return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
 
640
 
 
641
 
 
642
class GitObjectStoreContentCache(ContentCache):
 
643
 
 
644
    def __init__(self, store):
 
645
        self.store = store
 
646
 
 
647
    def add_multi(self, objs):
 
648
        self.store.add_objects(objs)
 
649
 
 
650
    def add(self, obj, path):
 
651
        self.store.add_object(obj)
 
652
 
 
653
    def __getitem__(self, sha):
 
654
        return self.store[sha]
 
655
 
 
656
 
 
657
class IndexCacheUpdater(CacheUpdater):
 
658
 
 
659
    def __init__(self, cache, rev):
 
660
        self.cache = cache
 
661
        self.revid = rev.revision_id
 
662
        self.parent_revids = rev.parent_ids
 
663
        self._commit = None
 
664
        self._entries = []
 
665
        self._cache_objs = set()
 
666
 
 
667
    def add_object(self, obj, ie, path):
 
668
        if obj.type_name == "commit":
 
669
            self._commit = obj
 
670
            assert type(ie) is dict
 
671
            self.cache.idmap._add_git_sha(obj.id, "commit",
 
672
                (self.revid, obj.tree, ie))
 
673
            self.cache.idmap._add_node(("commit", self.revid, "X"),
 
674
                " ".join((obj.id, obj.tree)))
 
675
            self._cache_objs.add((obj, path))
 
676
        elif obj.type_name == "blob":
 
677
            self.cache.idmap._add_git_sha(obj.id, "blob",
 
678
                (ie.file_id, ie.revision))
 
679
            self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
 
680
            if ie.kind == "symlink":
 
681
                self._cache_objs.add((obj, path))
 
682
        elif obj.type_name == "tree":
 
683
            self.cache.idmap._add_git_sha(obj.id, "tree",
 
684
                (ie.file_id, self.revid))
 
685
            self._cache_objs.add((obj, path))
 
686
        else:
 
687
            raise AssertionError
 
688
 
 
689
    def finish(self):
 
690
        self.cache.content_cache.add_multi(self._cache_objs)
 
691
        return self._commit
 
692
 
 
693
 
 
694
class IndexBzrGitCache(BzrGitCache):
 
695
 
 
696
    def __init__(self, transport=None):
 
697
        mapper = versionedfile.ConstantMapper("trees")
 
698
        shamap = IndexGitShaMap(transport.clone('index'))
 
699
        #trees_store = knit.make_file_factory(True, mapper)(transport)
 
700
        #content_cache = VersionedFilesContentCache(trees_store)
 
701
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
702
        store = TransportObjectStore(transport.clone('objects'))
 
703
        content_cache = GitObjectStoreContentCache(store)
 
704
        super(IndexBzrGitCache, self).__init__(shamap, content_cache,
 
705
                IndexCacheUpdater)
 
706
 
 
707
 
 
708
class IndexGitCacheFormat(BzrGitCacheFormat):
 
709
 
 
710
    def get_format_string(self):
 
711
        return 'bzr-git sha map with git object cache version 1\n'
 
712
 
 
713
    def initialize(self, transport):
 
714
        super(IndexGitCacheFormat, self).initialize(transport)
 
715
        transport.mkdir('index')
 
716
        transport.mkdir('objects')
 
717
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
718
        TransportObjectStore.init(transport.clone('objects'))
 
719
 
 
720
    def open(self, transport):
 
721
        return IndexBzrGitCache(transport)
 
722
 
 
723
 
 
724
class IndexGitShaMap(GitShaMap):
 
725
    """SHA Map that uses the Bazaar APIs to store a cache.
 
726
 
 
727
    BTree Index file with the following contents:
 
728
 
 
729
    ("git", <sha1>) -> "<type> <type-data1> <type-data2>"
 
730
    ("commit", <revid>) -> "<sha1> <tree-id>"
 
731
    ("blob", <fileid>, <revid>) -> <sha1>
 
732
 
 
733
    """
 
734
 
 
735
    def __init__(self, transport=None):
 
736
        if transport is None:
 
737
            self._transport = None
 
738
            self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
 
739
            self._builder = self._index
 
740
        else:
 
741
            self._builder = None
 
742
            self._transport = transport
 
743
            self._index = _mod_index.CombinedGraphIndex([])
 
744
            for name in self._transport.list_dir("."):
 
745
                if not name.endswith(".rix"):
 
746
                    continue
 
747
                x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
 
748
                    self._transport.stat(name).st_size)
 
749
                self._index.insert_index(0, x)
 
750
 
 
751
    @classmethod
 
752
    def from_repository(cls, repository):
 
753
        transport = getattr(repository, "_transport", None)
 
754
        if transport is not None:
 
755
            try:
 
756
                transport.mkdir('git')
 
757
            except bzrlib.errors.FileExists:
 
758
                pass
 
759
            return cls(transport.clone('git'))
 
760
        from bzrlib.transport import get_transport
 
761
        return cls(get_transport(get_cache_dir()))
 
762
 
 
763
    def __repr__(self):
 
764
        if self._transport is not None:
 
765
            return "%s(%r)" % (self.__class__.__name__, self._transport.base)
 
766
        else:
 
767
            return "%s()" % (self.__class__.__name__)
 
768
 
 
769
    def repack(self):
 
770
        assert self._builder is None
 
771
        self.start_write_group()
 
772
        for _, key, value in self._index.iter_all_entries():
 
773
            self._builder.add_node(key, value)
 
774
        to_remove = []
 
775
        for name in self._transport.list_dir('.'):
 
776
            if name.endswith('.rix'):
 
777
                to_remove.append(name)
 
778
        self.commit_write_group()
 
779
        del self._index.indices[1:]
 
780
        for name in to_remove:
 
781
            self._transport.rename(name, name + '.old')
 
782
 
 
783
    def start_write_group(self):
 
784
        assert self._builder is None
 
785
        self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
 
786
        self._name = osutils.sha()
 
787
 
 
788
    def commit_write_group(self):
 
789
        assert self._builder is not None
 
790
        stream = self._builder.finish()
 
791
        name = self._name.hexdigest() + ".rix"
 
792
        size = self._transport.put_file(name, stream)
 
793
        index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
 
794
        self._index.insert_index(0, index)
 
795
        self._builder = None
 
796
        self._name = None
 
797
 
 
798
    def abort_write_group(self):
 
799
        assert self._builder is not None
 
800
        self._builder = None
 
801
        self._name = None
 
802
 
 
803
    def _add_node(self, key, value):
 
804
        try:
 
805
            self._builder.add_node(key, value)
 
806
        except bzrlib.errors.BadIndexDuplicateKey:
 
807
            # Multiple bzr objects can have the same contents
 
808
            return True
 
809
        else:
 
810
            return False
 
811
 
 
812
    def _get_entry(self, key):
 
813
        entries = self._index.iter_entries([key])
 
814
        try:
 
815
            return entries.next()[2]
 
816
        except StopIteration:
 
817
            if self._builder is None:
 
818
                raise KeyError
 
819
            entries = self._builder.iter_entries([key])
 
820
            try:
 
821
                return entries.next()[2]
 
822
            except StopIteration:
 
823
                raise KeyError
 
824
 
 
825
    def _iter_keys_prefix(self, prefix):
 
826
        for entry in self._index.iter_entries_prefix([prefix]):
 
827
            yield entry[1]
 
828
        if self._builder is not None:
 
829
            for entry in self._builder.iter_entries_prefix([prefix]):
 
830
                yield entry[1]
 
831
 
 
832
    def lookup_commit(self, revid):
 
833
        return self._get_entry(("commit", revid, "X"))[:40]
 
834
 
 
835
    def _add_git_sha(self, hexsha, type, type_data):
 
836
        if hexsha is not None:
 
837
            self._name.update(hexsha)
 
838
            if type == "commit":
 
839
                td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
 
840
            else:
 
841
                td = type_data
 
842
            self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
 
843
        else:
 
844
            # This object is not represented in Git - perhaps an empty
 
845
            # directory?
 
846
            self._name.update(type + " ".join(type_data))
 
847
 
 
848
    def lookup_blob_id(self, fileid, revision):
 
849
        return self._get_entry(("blob", fileid, revision))
 
850
 
 
851
    def lookup_git_sha(self, sha):
 
852
        if len(sha) == 20:
 
853
            sha = sha_to_hex(sha)
 
854
        data = self._get_entry(("git", sha, "X")).split(" ", 3)
 
855
        if data[0] == "commit":
 
856
            return ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
 
857
        else:
 
858
            return (data[0], tuple(data[1:]))
 
859
 
 
860
    def revids(self):
 
861
        """List the revision ids known."""
 
862
        for key in self._iter_keys_prefix(("commit", None, None)):
 
863
            yield key[1]
 
864
 
 
865
    def missing_revisions(self, revids):
 
866
        """Return set of all the revisions that are not present."""
 
867
        missing_revids = set(revids)
 
868
        for _, key, value in self._index.iter_entries((
 
869
            ("commit", revid, "X") for revid in revids)):
 
870
            missing_revids.remove(key[1])
 
871
        return missing_revids
 
872
 
 
873
    def sha1s(self):
 
874
        """List the SHA1s."""
 
875
        for key in self._iter_keys_prefix(("git", None, None)):
 
876
            yield key[1]
 
877
 
 
878
 
559
879
formats = registry.Registry()
560
880
formats.register(TdbGitCacheFormat().get_format_string(),
561
881
    TdbGitCacheFormat())
562
882
formats.register(SqliteGitCacheFormat().get_format_string(),
563
883
    SqliteGitCacheFormat())
 
884
formats.register(IndexGitCacheFormat().get_format_string(),
 
885
    IndexGitCacheFormat())
 
886
# In the future, this will become the default:
 
887
# formats.register('default', IndexGitCacheFormat())
564
888
try:
565
889
    import tdb
566
890
except ImportError:
569
893
    formats.register('default', TdbGitCacheFormat())
570
894
 
571
895
 
 
896
 
572
897
def migrate_ancient_formats(repo_transport):
573
898
    # Prefer migrating git.db over git.tdb, since the latter may not 
574
899
    # be openable on some platforms.