1
# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Map from Git sha's to Bazaar objects."""
19
from dulwich.objects import (
26
from dulwich.objects import (
38
btree_index as _mod_btree_index,
42
from ..transport import (
43
get_transport_from_path,
48
path = os.path.join(bedding.cache_dir(), "git")
49
if not os.path.isdir(path):
54
def get_remote_cache_transport(repository):
55
"""Retrieve the transport to use when accessing (unwritable) remote
58
uuid = getattr(repository, "uuid", None)
60
path = get_cache_dir()
62
path = os.path.join(get_cache_dir(), uuid)
63
if not os.path.isdir(path):
65
return get_transport_from_path(path)
68
def check_pysqlite_version(sqlite3):
69
"""Check that sqlite library is compatible.
72
if (sqlite3.sqlite_version_info[0] < 3
73
or (sqlite3.sqlite_version_info[0] == 3 and
74
sqlite3.sqlite_version_info[1] < 3)):
75
trace.warning('Needs at least sqlite 3.3.x')
76
raise bzr_errors.BzrError("incompatible sqlite library")
82
check_pysqlite_version(sqlite3)
83
except (ImportError, bzr_errors.BzrError):
84
from pysqlite2 import dbapi2 as sqlite3
85
check_pysqlite_version(sqlite3)
87
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
89
raise bzr_errors.BzrError("missing sqlite library")
92
_mapdbs = threading.local()
96
"""Get a cache for this thread's db connections."""
99
except AttributeError:
104
class GitShaMap(object):
105
"""Git<->Bzr revision id mapping database."""
107
def lookup_git_sha(self, sha):
108
"""Lookup a Git sha in the database.
109
:param sha: Git object sha
110
:return: list with (type, type_data) tuples with type_data:
111
commit: revid, tree_sha, verifiers
115
raise NotImplementedError(self.lookup_git_sha)
117
def lookup_blob_id(self, file_id, revision):
118
"""Retrieve a Git blob SHA by file id.
120
:param file_id: File id of the file/symlink
121
:param revision: revision in which the file was last changed.
123
raise NotImplementedError(self.lookup_blob_id)
125
def lookup_tree_id(self, file_id, revision):
126
"""Retrieve a Git tree SHA by file id.
128
raise NotImplementedError(self.lookup_tree_id)
130
def lookup_commit(self, revid):
131
"""Retrieve a Git commit SHA by Bazaar revision id.
133
raise NotImplementedError(self.lookup_commit)
136
"""List the revision ids known."""
137
raise NotImplementedError(self.revids)
139
def missing_revisions(self, revids):
140
"""Return set of all the revisions that are not present."""
141
present_revids = set(self.revids())
142
if not isinstance(revids, set):
144
return revids - present_revids
147
"""List the SHA1s."""
148
raise NotImplementedError(self.sha1s)
150
def start_write_group(self):
151
"""Start writing changes."""
153
def commit_write_group(self):
154
"""Commit any pending changes."""
156
def abort_write_group(self):
157
"""Abort any pending changes."""
160
class ContentCache(object):
161
"""Object that can cache Git objects."""
163
def add(self, object):
165
raise NotImplementedError(self.add)
167
def add_multi(self, objects):
168
"""Add multiple objects."""
172
def __getitem__(self, sha):
173
"""Retrieve an item, by SHA."""
174
raise NotImplementedError(self.__getitem__)
177
class BzrGitCacheFormat(object):
178
"""Bazaar-Git Cache Format."""
180
def get_format_string(self):
181
"""Return a single-line unique format string for this cache format."""
182
raise NotImplementedError(self.get_format_string)
184
def open(self, transport):
185
"""Open this format on a transport."""
186
raise NotImplementedError(self.open)
188
def initialize(self, transport):
189
"""Create a new instance of this cache format at transport."""
190
transport.put_bytes('format', self.get_format_string())
193
def from_transport(self, transport):
194
"""Open a cache file present on a transport, or initialize one.
196
:param transport: Transport to use
197
:return: A BzrGitCache instance
200
format_name = transport.get_bytes('format')
201
format = formats.get(format_name)
202
except bzr_errors.NoSuchFile:
203
format = formats.get('default')
204
format.initialize(transport)
205
return format.open(transport)
208
def from_repository(cls, repository):
209
"""Open a cache file for a repository.
211
This will use the repository's transport to store the cache file, or
212
use the users global cache directory if the repository has no
213
transport associated with it.
215
:param repository: Repository to open the cache for
216
:return: A `BzrGitCache`
218
from ..transport.local import LocalTransport
219
repo_transport = getattr(repository, "_transport", None)
220
if (repo_transport is not None
221
and isinstance(repo_transport, LocalTransport)):
222
# Even if we don't write to this repo, we should be able
223
# to update its cache.
225
repo_transport = remove_readonly_transport_decorator(
227
except bzr_errors.ReadOnlyError:
231
repo_transport.mkdir('git')
232
except bzr_errors.FileExists:
234
transport = repo_transport.clone('git')
237
if transport is None:
238
transport = get_remote_cache_transport(repository)
239
return cls.from_transport(transport)
242
class CacheUpdater(object):
243
"""Base class for objects that can update a bzr-git cache."""
245
def add_object(self, obj, bzr_key_data, path):
248
:param obj: Object type ("commit", "blob" or "tree")
249
:param bzr_key_data: bzr key store data or testament_sha in case
251
:param path: Path of the object (optional)
253
raise NotImplementedError(self.add_object)
256
raise NotImplementedError(self.finish)
259
class BzrGitCache(object):
260
"""Caching backend."""
262
def __init__(self, idmap, cache_updater_klass):
264
self._cache_updater_klass = cache_updater_klass
266
def get_updater(self, rev):
267
"""Update an object that implements the CacheUpdater interface for
270
return self._cache_updater_klass(self, rev)
273
def DictBzrGitCache():
274
return BzrGitCache(DictGitShaMap(), DictCacheUpdater)
277
class DictCacheUpdater(CacheUpdater):
278
"""Cache updater for dict-based caches."""
280
def __init__(self, cache, rev):
282
self.revid = rev.revision_id
283
self.parent_revids = rev.parent_ids
287
def add_object(self, obj, bzr_key_data, path):
288
if isinstance(obj, tuple):
289
(type_name, hexsha) = obj
291
type_name = obj.type_name.decode('ascii')
293
if not isinstance(hexsha, bytes):
294
raise TypeError(hexsha)
295
if type_name == "commit":
297
if type(bzr_key_data) is not dict:
298
raise TypeError(bzr_key_data)
300
type_data = (self.revid, self._commit.tree, bzr_key_data)
301
self.cache.idmap._by_revid[self.revid] = hexsha
302
elif type_name in ("blob", "tree"):
303
if bzr_key_data is not None:
304
key = type_data = bzr_key_data
305
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
306
type_data[0]] = hexsha
309
entry = (type_name, type_data)
310
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
313
if self._commit is None:
314
raise AssertionError("No commit object added")
318
class DictGitShaMap(GitShaMap):
319
"""Git SHA map that uses a dictionary."""
326
def lookup_blob_id(self, fileid, revision):
327
return self._by_fileid[revision][fileid]
329
def lookup_git_sha(self, sha):
330
if not isinstance(sha, bytes):
332
for entry in self._by_sha[sha].values():
335
def lookup_tree_id(self, fileid, revision):
336
return self._by_fileid[revision][fileid]
338
def lookup_commit(self, revid):
339
return self._by_revid[revid]
342
for key, entries in self._by_sha.items():
343
for (type, type_data) in entries.values():
348
return self._by_sha.keys()
351
class SqliteCacheUpdater(CacheUpdater):
353
def __init__(self, cache, rev):
355
self.db = self.cache.idmap.db
356
self.revid = rev.revision_id
361
def add_object(self, obj, bzr_key_data, path):
362
if isinstance(obj, tuple):
363
(type_name, hexsha) = obj
365
type_name = obj.type_name.decode('ascii')
367
if not isinstance(hexsha, bytes):
368
raise TypeError(hexsha)
369
if type_name == "commit":
371
if type(bzr_key_data) is not dict:
372
raise TypeError(bzr_key_data)
373
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
374
elif type_name == "tree":
375
if bzr_key_data is not None:
376
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
377
elif type_name == "blob":
378
if bzr_key_data is not None:
379
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
384
if self._commit is None:
385
raise AssertionError("No commit object added")
387
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
390
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
393
"replace into commits (sha1, revid, tree_sha, testament3_sha1) "
394
"values (?, ?, ?, ?)",
395
(self._commit.id, self.revid, self._commit.tree,
396
self._testament3_sha1))
400
def SqliteBzrGitCache(p):
401
return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
404
class SqliteGitCacheFormat(BzrGitCacheFormat):
406
def get_format_string(self):
407
return b'bzr-git sha map version 1 using sqlite\n'
409
def open(self, transport):
411
basepath = transport.local_abspath(".")
412
except bzr_errors.NotLocalUrl:
413
basepath = get_cache_dir()
414
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
417
class SqliteGitShaMap(GitShaMap):
418
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
420
def __init__(self, path=None):
423
self.db = sqlite3.connect(":memory:")
425
if path not in mapdbs():
426
mapdbs()[path] = sqlite3.connect(path)
427
self.db = mapdbs()[path]
428
self.db.text_factory = str
429
self.db.executescript("""
430
create table if not exists commits(
431
sha1 text not null check(length(sha1) == 40),
433
tree_sha text not null check(length(tree_sha) == 40)
435
create index if not exists commit_sha1 on commits(sha1);
436
create unique index if not exists commit_revid on commits(revid);
437
create table if not exists blobs(
438
sha1 text not null check(length(sha1) == 40),
439
fileid text not null,
442
create index if not exists blobs_sha1 on blobs(sha1);
443
create unique index if not exists blobs_fileid_revid on blobs(
445
create table if not exists trees(
446
sha1 text unique not null check(length(sha1) == 40),
447
fileid text not null,
450
create unique index if not exists trees_sha1 on trees(sha1);
451
create unique index if not exists trees_fileid_revid on trees(
455
self.db.executescript(
456
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
457
except sqlite3.OperationalError:
458
pass # Column already exists.
461
return "%s(%r)" % (self.__class__.__name__, self.path)
463
def lookup_commit(self, revid):
464
cursor = self.db.execute("select sha1 from commits where revid = ?",
466
row = cursor.fetchone()
471
def commit_write_group(self):
474
def lookup_blob_id(self, fileid, revision):
475
row = self.db.execute(
476
"select sha1 from blobs where fileid = ? and revid = ?",
477
(fileid, revision)).fetchone()
480
raise KeyError(fileid)
482
def lookup_tree_id(self, fileid, revision):
483
row = self.db.execute(
484
"select sha1 from trees where fileid = ? and revid = ?",
485
(fileid, revision)).fetchone()
488
raise KeyError(fileid)
490
def lookup_git_sha(self, sha):
491
"""Lookup a Git sha in the database.
493
:param sha: Git object sha
494
:return: (type, type_data) with type_data:
495
commit: revid, tree sha, verifiers
500
cursor = self.db.execute(
501
"select revid, tree_sha, testament3_sha1 from commits where "
503
for row in cursor.fetchall():
505
if row[2] is not None:
506
verifiers = {"testament3-sha1": row[2]}
509
yield ("commit", (row[0], row[1], verifiers))
510
cursor = self.db.execute(
511
"select fileid, revid from blobs where sha1 = ?", (sha,))
512
for row in cursor.fetchall():
515
cursor = self.db.execute(
516
"select fileid, revid from trees where sha1 = ?", (sha,))
517
for row in cursor.fetchall():
524
"""List the revision ids known."""
525
return (row for (row,) in self.db.execute("select revid from commits"))
528
"""List the SHA1s."""
529
for table in ("blobs", "commits", "trees"):
530
for (sha,) in self.db.execute("select sha1 from %s" % table):
531
yield sha.encode('ascii')
534
class TdbCacheUpdater(CacheUpdater):
535
"""Cache updater for tdb-based caches."""
537
def __init__(self, cache, rev):
539
self.db = cache.idmap.db
540
self.revid = rev.revision_id
541
self.parent_revids = rev.parent_ids
545
def add_object(self, obj, bzr_key_data, path):
546
if isinstance(obj, tuple):
547
(type_name, hexsha) = obj
548
sha = hex_to_sha(hexsha)
550
type_name = obj.type_name.decode('ascii')
551
sha = obj.sha().digest()
552
if type_name == "commit":
553
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
554
if type(bzr_key_data) is not dict:
555
raise TypeError(bzr_key_data)
556
type_data = (self.revid, obj.tree)
558
type_data += (bzr_key_data["testament3-sha1"],)
562
elif type_name == "blob":
563
if bzr_key_data is None:
566
(b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
567
type_data = bzr_key_data
568
elif type_name == "tree":
569
if bzr_key_data is None:
571
type_data = bzr_key_data
574
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
577
oldval = self.db[key]
581
if not oldval.endswith(b'\n'):
582
self.db[key] = b"".join([oldval, b"\n", entry])
584
self.db[key] = b"".join([oldval, entry])
587
if self._commit is None:
588
raise AssertionError("No commit object added")
592
def TdbBzrGitCache(p):
593
return BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
596
class TdbGitCacheFormat(BzrGitCacheFormat):
597
"""Cache format for tdb-based caches."""
599
def get_format_string(self):
600
return b'bzr-git sha map version 3 using tdb\n'
602
def open(self, transport):
604
basepath = transport.local_abspath(".")
605
except bzr_errors.NotLocalUrl:
606
basepath = get_cache_dir()
608
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
611
"Unable to open existing bzr-git cache because 'tdb' is not "
615
class TdbGitShaMap(GitShaMap):
616
"""SHA Map that uses a TDB database.
620
"git <sha1>" -> "<type> <type-data1> <type-data2>"
621
"commit revid" -> "<sha1> <tree-id>"
622
"tree fileid revid" -> "<sha1>"
623
"blob fileid revid" -> "<sha1>"
627
TDB_HASH_SIZE = 50000
629
def __init__(self, path=None):
635
if path not in mapdbs():
636
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
637
os.O_RDWR | os.O_CREAT)
638
self.db = mapdbs()[path]
640
if int(self.db[b"version"]) not in (2, 3):
642
"SHA Map is incompatible (%s -> %d), rebuilding database.",
643
self.db[b"version"], self.TDB_MAP_VERSION)
647
self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
649
def start_write_group(self):
650
"""Start writing changes."""
651
self.db.transaction_start()
653
def commit_write_group(self):
654
"""Commit any pending changes."""
655
self.db.transaction_commit()
657
def abort_write_group(self):
658
"""Abort any pending changes."""
659
self.db.transaction_cancel()
662
return "%s(%r)" % (self.__class__.__name__, self.path)
664
def lookup_commit(self, revid):
666
return sha_to_hex(self.db[b"commit\0" + revid][:20])
668
raise KeyError("No cache entry for %r" % revid)
670
def lookup_blob_id(self, fileid, revision):
671
return sha_to_hex(self.db[b"\0".join((b"blob", fileid, revision))])
673
def lookup_git_sha(self, sha):
674
"""Lookup a Git sha in the database.
676
:param sha: Git object sha
677
:return: (type, type_data) with type_data:
678
commit: revid, tree sha
683
sha = hex_to_sha(sha)
684
value = self.db[b"git\0" + sha]
685
for data in value.splitlines():
686
data = data.split(b"\0")
687
type_name = data[0].decode('ascii')
688
if type_name == "commit":
690
yield (type_name, (data[1], data[2], {}))
692
yield (type_name, (data[1], data[2],
693
{"testament3-sha1": data[3]}))
694
elif type_name in ("tree", "blob"):
695
yield (type_name, tuple(data[1:]))
697
raise AssertionError("unknown type %r" % type_name)
699
def missing_revisions(self, revids):
702
if self.db.get(b"commit\0" + revid) is None:
707
return self.db.keys()
710
"""List the revision ids known."""
711
for key in self._keys():
712
if key.startswith(b"commit\0"):
716
"""List the SHA1s."""
717
for key in self._keys():
718
if key.startswith(b"git\0"):
719
yield sha_to_hex(key[4:])
722
class VersionedFilesContentCache(ContentCache):
724
def __init__(self, vf):
728
self._vf.insert_record_stream(
729
[versionedfile.ChunkedContentFactory(
730
(obj.id,), [], None, obj.as_legacy_object_chunks())])
732
def __getitem__(self, sha):
733
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
735
if entry.storage_kind == 'absent':
737
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
740
class IndexCacheUpdater(CacheUpdater):
742
def __init__(self, cache, rev):
744
self.revid = rev.revision_id
745
self.parent_revids = rev.parent_ids
749
def add_object(self, obj, bzr_key_data, path):
750
if isinstance(obj, tuple):
751
(type_name, hexsha) = obj
753
type_name = obj.type_name.decode('ascii')
755
if type_name == "commit":
757
if type(bzr_key_data) is not dict:
758
raise TypeError(bzr_key_data)
759
self.cache.idmap._add_git_sha(hexsha, b"commit",
760
(self.revid, obj.tree, bzr_key_data))
761
self.cache.idmap._add_node((b"commit", self.revid, b"X"),
762
b" ".join((hexsha, obj.tree)))
763
elif type_name == "blob":
764
self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
765
self.cache.idmap._add_node((b"blob", bzr_key_data[0],
766
bzr_key_data[1]), hexsha)
767
elif type_name == "tree":
768
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
776
class IndexBzrGitCache(BzrGitCache):
778
def __init__(self, transport=None):
779
shamap = IndexGitShaMap(transport.clone('index'))
780
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
783
class IndexGitCacheFormat(BzrGitCacheFormat):
785
def get_format_string(self):
786
return b'bzr-git sha map with git object cache version 1\n'
788
def initialize(self, transport):
789
super(IndexGitCacheFormat, self).initialize(transport)
790
transport.mkdir('index')
791
transport.mkdir('objects')
792
from .transportgit import TransportObjectStore
793
TransportObjectStore.init(transport.clone('objects'))
795
def open(self, transport):
796
return IndexBzrGitCache(transport)
799
class IndexGitShaMap(GitShaMap):
800
"""SHA Map that uses the Bazaar APIs to store a cache.
802
BTree Index file with the following contents:
804
("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
805
("commit", <revid>, "X") -> "<sha1> <tree-id>"
806
("blob", <fileid>, <revid>) -> <sha1>
810
def __init__(self, transport=None):
812
if transport is None:
813
self._transport = None
814
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
815
self._builder = self._index
818
self._transport = transport
819
self._index = _mod_index.CombinedGraphIndex([])
820
for name in self._transport.list_dir("."):
821
if not name.endswith(".rix"):
823
x = _mod_btree_index.BTreeGraphIndex(
824
self._transport, name, self._transport.stat(name).st_size)
825
self._index.insert_index(0, x)
828
def from_repository(cls, repository):
829
transport = getattr(repository, "_transport", None)
830
if transport is not None:
832
transport.mkdir('git')
833
except bzr_errors.FileExists:
835
return cls(transport.clone('git'))
836
return cls(get_transport_from_path(get_cache_dir()))
839
if self._transport is not None:
840
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
842
return "%s()" % (self.__class__.__name__)
845
if self._builder is not None:
846
raise bzr_errors.BzrError('builder already open')
847
self.start_write_group()
848
self._builder.add_nodes(
849
((key, value) for (_, key, value) in
850
self._index.iter_all_entries()))
852
for name in self._transport.list_dir('.'):
853
if name.endswith('.rix'):
854
to_remove.append(name)
855
self.commit_write_group()
856
del self._index.indices[1:]
857
for name in to_remove:
858
self._transport.rename(name, name + '.old')
860
def start_write_group(self):
861
if self._builder is not None:
862
raise bzr_errors.BzrError('builder already open')
863
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
864
self._name = osutils.sha()
866
def commit_write_group(self):
867
if self._builder is None:
868
raise bzr_errors.BzrError('builder not open')
869
stream = self._builder.finish()
870
name = self._name.hexdigest() + ".rix"
871
size = self._transport.put_file(name, stream)
872
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
873
self._index.insert_index(0, index)
877
def abort_write_group(self):
878
if self._builder is None:
879
raise bzr_errors.BzrError('builder not open')
883
def _add_node(self, key, value):
887
self._builder.add_node(key, value)
892
def _get_entry(self, key):
893
entries = self._index.iter_entries([key])
895
return next(entries)[2]
896
except StopIteration:
897
if self._builder is None:
899
entries = self._builder.iter_entries([key])
901
return next(entries)[2]
902
except StopIteration:
905
def _iter_entries_prefix(self, prefix):
906
for entry in self._index.iter_entries_prefix([prefix]):
907
yield (entry[1], entry[2])
908
if self._builder is not None:
909
for entry in self._builder.iter_entries_prefix([prefix]):
910
yield (entry[1], entry[2])
912
def lookup_commit(self, revid):
913
return self._get_entry((b"commit", revid, b"X"))[:40]
915
def _add_git_sha(self, hexsha, type, type_data):
916
if hexsha is not None:
917
self._name.update(hexsha)
918
if type == b"commit":
919
td = (type_data[0], type_data[1])
921
td += (type_data[2]["testament3-sha1"],)
926
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
928
# This object is not represented in Git - perhaps an empty
930
self._name.update(type + b" ".join(type_data))
932
def lookup_blob_id(self, fileid, revision):
933
return self._get_entry((b"blob", fileid, revision))
935
def lookup_git_sha(self, sha):
937
sha = sha_to_hex(sha)
938
value = self._get_entry((b"git", sha, b"X"))
939
data = value.split(b" ", 3)
940
if data[0] == b"commit":
943
verifiers = {"testament3-sha1": data[3]}
948
yield ("commit", (data[1], data[2], verifiers))
950
yield (data[0].decode('ascii'), tuple(data[1:]))
953
"""List the revision ids known."""
954
for key, value in self._iter_entries_prefix((b"commit", None, None)):
957
def missing_revisions(self, revids):
958
"""Return set of all the revisions that are not present."""
959
missing_revids = set(revids)
960
for _, key, value in self._index.iter_entries((
961
(b"commit", revid, b"X") for revid in revids)):
962
missing_revids.remove(key[1])
963
return missing_revids
966
"""List the SHA1s."""
967
for key, value in self._iter_entries_prefix((b"git", None, None)):
971
formats = registry.Registry()
972
formats.register(TdbGitCacheFormat().get_format_string(),
974
formats.register(SqliteGitCacheFormat().get_format_string(),
975
SqliteGitCacheFormat())
976
formats.register(IndexGitCacheFormat().get_format_string(),
977
IndexGitCacheFormat())
978
# In the future, this will become the default:
979
formats.register('default', IndexGitCacheFormat())
982
def migrate_ancient_formats(repo_transport):
983
# Migrate older cache formats
984
repo_transport = remove_readonly_transport_decorator(repo_transport)
985
has_sqlite = repo_transport.has("git.db")
986
has_tdb = repo_transport.has("git.tdb")
987
if not has_sqlite or has_tdb:
990
repo_transport.mkdir("git")
991
except bzr_errors.FileExists:
993
# Prefer migrating git.db over git.tdb, since the latter may not
994
# be openable on some platforms.
996
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
997
repo_transport.rename("git.db", "git/idmap.db")
999
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
1000
repo_transport.rename("git.tdb", "git/idmap.tdb")
1003
def remove_readonly_transport_decorator(transport):
1004
if transport.is_readonly():
1006
return transport._decorated
1007
except AttributeError:
1008
raise bzr_errors.ReadOnlyError(transport)
1012
def from_repository(repository):
1013
"""Open a cache file for a repository.
1015
If the repository is remote and there is no transport available from it
1016
this will use a local file in the users cache directory
1017
(typically ~/.cache/bazaar/git/)
1019
:param repository: A repository object
1021
repo_transport = getattr(repository, "_transport", None)
1022
if repo_transport is not None:
1024
migrate_ancient_formats(repo_transport)
1025
except bzr_errors.ReadOnlyError:
1026
pass # Not much we can do
1027
return BzrGitCacheFormat.from_repository(repository)