/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):
 
169
        """Return a single-line unique format string for this cache format."""
146
170
        raise NotImplementedError(self.get_format_string)
147
171
 
148
172
    def open(self, transport):
 
173
        """Open this format on a transport."""
149
174
        raise NotImplementedError(self.open)
150
175
 
151
176
    def initialize(self, transport):
 
177
        """Create a new instance of this cache format at transport."""
152
178
        transport.put_bytes('format', self.get_format_string())
153
179
 
154
180
    @classmethod
155
 
    def from_repository(self, repository):
156
 
        repo_transport = getattr(repository, "_transport", None)
157
 
        if repo_transport is not None:
158
 
            try:
159
 
                repo_transport.mkdir('git')
160
 
            except bzrlib.errors.FileExists:
161
 
                pass
162
 
            transport = repo_transport.clone('git')
163
 
        else:
164
 
            transport = get_remote_cache_transport()
 
181
    def from_transport(self, transport):
 
182
        """Open a cache file present on a transport, or initialize one.
 
183
 
 
184
        :param transport: Transport to use
 
185
        :return: A BzrGitCache instance
 
186
        """
165
187
        try:
166
188
            format_name = transport.get_bytes('format')
167
189
            format = formats.get(format_name)
170
192
            format.initialize(transport)
171
193
        return format.open(transport)
172
194
 
 
195
    @classmethod
 
196
    def from_repository(cls, repository):
 
197
        """Open a cache file for a repository.
 
198
 
 
199
        This will use the repository's transport to store the cache file, or
 
200
        use the users global cache directory if the repository has no 
 
201
        transport associated with it.
 
202
 
 
203
        :param repository: Repository to open the cache for
 
204
        :return: A `BzrGitCache`
 
205
        """
 
206
        repo_transport = getattr(repository, "_transport", None)
 
207
        if repo_transport is not None:
 
208
            # Even if we don't write to this repo, we should be able 
 
209
            # to update its cache.
 
210
            repo_transport = remove_readonly_transport_decorator(repo_transport)
 
211
            try:
 
212
                repo_transport.mkdir('git')
 
213
            except bzrlib.errors.FileExists:
 
214
                pass
 
215
            transport = repo_transport.clone('git')
 
216
        else:
 
217
            transport = get_remote_cache_transport()
 
218
        return cls.from_transport(transport)
 
219
 
173
220
 
174
221
class CacheUpdater(object):
175
 
 
176
 
    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
        """
177
232
        raise NotImplementedError(self.add_object)
178
233
 
179
234
    def finish(self):
189
244
        self._cache_updater_klass = cache_updater_klass
190
245
 
191
246
    def get_updater(self, rev):
 
247
        """Update an object that implements the CacheUpdater interface for 
 
248
        updating this cache.
 
249
        """
192
250
        return self._cache_updater_klass(self, rev)
193
251
 
194
252
 
196
254
 
197
255
 
198
256
class DictCacheUpdater(CacheUpdater):
 
257
    """Cache updater for dict-based caches."""
199
258
 
200
259
    def __init__(self, cache, rev):
201
260
        self.cache = cache
204
263
        self._commit = None
205
264
        self._entries = []
206
265
 
207
 
    def add_object(self, obj, ie):
 
266
    def add_object(self, obj, ie, path):
208
267
        if obj.type_name == "commit":
209
268
            self._commit = obj
210
 
            assert ie is None
211
 
            type_data = (self.revid, self._commit.tree)
 
269
            assert type(ie) is dict
 
270
            type_data = (self.revid, self._commit.tree, ie)
 
271
            self.cache.idmap._by_revid[self.revid] = obj.id
212
272
        elif obj.type_name in ("blob", "tree"):
213
 
            if obj.type_name == "blob":
214
 
                revision = ie.revision
215
 
            else:
216
 
                revision = self.revid
217
 
            type_data = (ie.file_id, revision)
218
 
            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
219
280
        else:
220
281
            raise AssertionError
221
282
        self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
227
288
 
228
289
 
229
290
class DictGitShaMap(GitShaMap):
 
291
    """Git SHA map that uses a dictionary."""
230
292
 
231
293
    def __init__(self):
232
294
        self._by_sha = {}
233
295
        self._by_fileid = {}
 
296
        self._by_revid = {}
234
297
 
235
298
    def lookup_blob_id(self, fileid, revision):
236
299
        return self._by_fileid[revision][fileid]
239
302
        return self._by_sha[sha]
240
303
 
241
304
    def lookup_tree_id(self, fileid, revision):
242
 
        return self._base._by_fileid[revision][fileid]
 
305
        return self._by_fileid[revision][fileid]
 
306
 
 
307
    def lookup_commit(self, revid):
 
308
        return self._by_revid[revid]
243
309
 
244
310
    def revids(self):
245
311
        for key, (type, type_data) in self._by_sha.iteritems():
260
326
        self._trees = []
261
327
        self._blobs = []
262
328
 
263
 
    def add_object(self, obj, ie):
 
329
    def add_object(self, obj, ie, path):
264
330
        if obj.type_name == "commit":
265
331
            self._commit = obj
266
 
            assert ie is None
 
332
            self._testament3_sha1 = ie["testament3-sha1"]
 
333
            assert type(ie) is dict
267
334
        elif obj.type_name == "tree":
268
 
            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))
269
337
        elif obj.type_name == "blob":
270
 
            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))
271
340
        else:
272
341
            raise AssertionError
273
342
 
281
350
            "replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
282
351
            self._blobs)
283
352
        self.db.execute(
284
 
            "replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
285
 
            (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))
286
355
        return self._commit
287
356
 
288
357
 
303
372
 
304
373
 
305
374
class SqliteGitShaMap(GitShaMap):
 
375
    """Bazaar GIT Sha map that uses a sqlite database for storage."""
306
376
 
307
377
    def __init__(self, path=None):
308
378
        self.path = path
336
406
        create unique index if not exists trees_sha1 on trees(sha1);
337
407
        create unique index if not exists trees_fileid_revid on trees(fileid, revid);
338
408
""")
 
409
        try:
 
410
            self.db.executescript(
 
411
                "ALTER TABLE commits ADD testament3_sha1 TEXT;")
 
412
        except sqlite3.OperationalError:
 
413
            pass # Column already exists.
339
414
 
340
415
    def __repr__(self):
341
416
        return "%s(%r)" % (self.__class__.__name__, self.path)
342
 
    
 
417
 
343
418
    def lookup_commit(self, revid):
344
 
        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()
345
422
        if row is not None:
346
423
            return row[0]
347
424
        raise KeyError
356
433
        raise KeyError(fileid)
357
434
 
358
435
    def lookup_tree_id(self, fileid, revision):
359
 
        row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, self.revid)).fetchone()
 
436
        row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
360
437
        if row is not None:
361
438
            return row[0]
362
439
        raise KeyError(fileid)
366
443
 
367
444
        :param sha: Git object sha
368
445
        :return: (type, type_data) with type_data:
369
 
            revision: revid, tree sha
 
446
            commit: revid, tree sha, verifiers
 
447
            tree: fileid, revid
 
448
            blob: fileid, revid
370
449
        """
371
 
        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()
372
451
        if row is not None:
373
 
            return ("commit", row)
 
452
            return ("commit", (row[0], row[1], {"testament3-sha1": row[2]}))
374
453
        row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
375
454
        if row is not None:
376
455
            return ("blob", row)
391
470
 
392
471
 
393
472
class TdbCacheUpdater(CacheUpdater):
 
473
    """Cache updater for tdb-based caches."""
394
474
 
395
475
    def __init__(self, cache, rev):
396
476
        self.cache = cache
400
480
        self._commit = None
401
481
        self._entries = []
402
482
 
403
 
    def add_object(self, obj, ie):
 
483
    def add_object(self, obj, ie, path):
404
484
        sha = obj.sha().digest()
405
485
        if obj.type_name == "commit":
406
 
            self.db["commit\0" + self.revid] = "\0".join((obj.id, obj.tree))
407
 
            type_data = (self.revid, obj.tree)
 
486
            self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
 
487
            assert type(ie) is dict, "was %r" % ie
 
488
            type_data = (self.revid, obj.tree, ie["testament3-sha1"])
408
489
            self._commit = obj
409
 
            assert ie is None
410
490
        elif obj.type_name == "blob":
 
491
            if ie is None:
 
492
                return
411
493
            self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
412
494
            type_data = (ie.file_id, ie.revision)
413
495
        elif obj.type_name == "tree":
 
496
            if ie is None:
 
497
                return
414
498
            type_data = (ie.file_id, self.revid)
415
499
        else:
416
500
            raise AssertionError
417
 
        self.db["git\0" + sha] = "\0".join((obj.type_name,
418
 
            type_data[0], type_data[1]))
 
501
        self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
419
502
 
420
503
    def finish(self):
421
504
        if self._commit is None:
426
509
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
427
510
 
428
511
class TdbGitCacheFormat(BzrGitCacheFormat):
 
512
    """Cache format for tdb-based caches."""
429
513
 
430
514
    def get_format_string(self):
431
515
        return 'bzr-git sha map version 3 using tdb\n'
496
580
 
497
581
    def lookup_blob_id(self, fileid, revision):
498
582
        return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
499
 
                
 
583
 
500
584
    def lookup_git_sha(self, sha):
501
585
        """Lookup a Git sha in the database.
502
586
 
503
587
        :param sha: Git object sha
504
588
        :return: (type, type_data) with type_data:
505
 
            revision: revid, tree sha
 
589
            commit: revid, tree sha
 
590
            blob: fileid, revid
 
591
            tree: fileid, revid
506
592
        """
507
593
        if len(sha) == 40:
508
594
            sha = hex_to_sha(sha)
509
595
        data = self.db["git\0" + sha].split("\0")
510
 
        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:]))
511
603
 
512
604
    def missing_revisions(self, revids):
513
605
        ret = set()
529
621
                yield sha_to_hex(key[4:])
530
622
 
531
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
 
532
879
formats = registry.Registry()
533
880
formats.register(TdbGitCacheFormat().get_format_string(),
534
881
    TdbGitCacheFormat())
535
882
formats.register(SqliteGitCacheFormat().get_format_string(),
536
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())
537
888
try:
538
889
    import tdb
539
890
except ImportError:
542
893
    formats.register('default', TdbGitCacheFormat())
543
894
 
544
895
 
 
896
 
545
897
def migrate_ancient_formats(repo_transport):
546
 
    if repo_transport.has("git.tdb"):
 
898
    # Prefer migrating git.db over git.tdb, since the latter may not 
 
899
    # be openable on some platforms.
 
900
    if repo_transport.has("git.db"):
 
901
        SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
 
902
        repo_transport.rename("git.db", "git/idmap.db")
 
903
    elif repo_transport.has("git.tdb"):
547
904
        TdbGitCacheFormat().initialize(repo_transport.clone("git"))
548
905
        repo_transport.rename("git.tdb", "git/idmap.tdb")
549
 
    elif repo_transport.has("git.db"):
550
 
        SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
551
 
        repo_transport.rename("git.db", "git/idmap.db")
 
906
 
 
907
 
 
908
def remove_readonly_transport_decorator(transport):
 
909
    if transport.is_readonly():
 
910
        return transport._decorated
 
911
    return transport
552
912
 
553
913
 
554
914
def from_repository(repository):
 
915
    """Open a cache file for a repository.
 
916
 
 
917
    If the repository is remote and there is no transport available from it
 
918
    this will use a local file in the users cache directory
 
919
    (typically ~/.cache/bazaar/git/)
 
920
 
 
921
    :param repository: A repository object
 
922
    """
555
923
    repo_transport = getattr(repository, "_transport", None)
556
924
    if repo_transport is not None:
557
925
        # Migrate older cache formats
 
926
        repo_transport = remove_readonly_transport_decorator(repo_transport)
558
927
        try:
559
928
            repo_transport.mkdir("git")
560
929
        except bzrlib.errors.FileExists: