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 __future__ import absolute_import
21
from dulwich.objects import (
28
from dulwich.objects import (
39
btree_index as _mod_btree_index,
43
from ..transport import (
50
from xdg.BaseDirectory import xdg_cache_home
52
from ..config import config_dir
53
ret = os.path.join(config_dir(), "git")
55
ret = os.path.join(xdg_cache_home, "breezy", "git")
56
if not os.path.isdir(ret):
61
def get_remote_cache_transport(repository):
62
"""Retrieve the transport to use when accessing (unwritable) remote
65
uuid = getattr(repository, "uuid", None)
67
path = get_cache_dir()
69
path = os.path.join(get_cache_dir(), uuid)
70
if not os.path.isdir(path):
72
return get_transport(path)
75
def check_pysqlite_version(sqlite3):
76
"""Check that sqlite library is compatible.
79
if (sqlite3.sqlite_version_info[0] < 3 or
80
(sqlite3.sqlite_version_info[0] == 3 and
81
sqlite3.sqlite_version_info[1] < 3)):
82
trace.warning('Needs at least sqlite 3.3.x')
83
raise bzr_errors.BzrError("incompatible sqlite library")
88
check_pysqlite_version(sqlite3)
89
except (ImportError, bzr_errors.BzrError) as e:
90
from pysqlite2 import dbapi2 as sqlite3
91
check_pysqlite_version(sqlite3)
93
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
95
raise bzr_errors.BzrError("missing sqlite library")
98
_mapdbs = threading.local()
100
"""Get a cache for this thread's db connections."""
103
except AttributeError:
108
class GitShaMap(object):
109
"""Git<->Bzr revision id mapping database."""
111
def lookup_git_sha(self, sha):
112
"""Lookup a Git sha in the database.
113
:param sha: Git object sha
114
:return: list with (type, type_data) tuples with type_data:
115
commit: revid, tree_sha, verifiers
119
raise NotImplementedError(self.lookup_git_sha)
121
def lookup_blob_id(self, file_id, revision):
122
"""Retrieve a Git blob SHA by file id.
124
:param file_id: File id of the file/symlink
125
:param revision: revision in which the file was last changed.
127
raise NotImplementedError(self.lookup_blob_id)
129
def lookup_tree_id(self, file_id, revision):
130
"""Retrieve a Git tree SHA by file id.
132
raise NotImplementedError(self.lookup_tree_id)
134
def lookup_commit(self, revid):
135
"""Retrieve a Git commit SHA by Bazaar revision id.
137
raise NotImplementedError(self.lookup_commit)
140
"""List the revision ids known."""
141
raise NotImplementedError(self.revids)
143
def missing_revisions(self, revids):
144
"""Return set of all the revisions that are not present."""
145
present_revids = set(self.revids())
146
if not isinstance(revids, set):
148
return revids - present_revids
151
"""List the SHA1s."""
152
raise NotImplementedError(self.sha1s)
154
def start_write_group(self):
155
"""Start writing changes."""
157
def commit_write_group(self):
158
"""Commit any pending changes."""
160
def abort_write_group(self):
161
"""Abort any pending changes."""
164
class ContentCache(object):
165
"""Object that can cache Git objects."""
167
def add(self, object):
169
raise NotImplementedError(self.add)
171
def add_multi(self, objects):
172
"""Add multiple objects."""
176
def __getitem__(self, sha):
177
"""Retrieve an item, by SHA."""
178
raise NotImplementedError(self.__getitem__)
181
class BzrGitCacheFormat(object):
182
"""Bazaar-Git Cache Format."""
184
def get_format_string(self):
185
"""Return a single-line unique format string for this cache format."""
186
raise NotImplementedError(self.get_format_string)
188
def open(self, transport):
189
"""Open this format on a transport."""
190
raise NotImplementedError(self.open)
192
def initialize(self, transport):
193
"""Create a new instance of this cache format at transport."""
194
transport.put_bytes('format', self.get_format_string())
197
def from_transport(self, transport):
198
"""Open a cache file present on a transport, or initialize one.
200
:param transport: Transport to use
201
:return: A BzrGitCache instance
204
format_name = transport.get_bytes('format')
205
format = formats.get(format_name)
206
except bzr_errors.NoSuchFile:
207
format = formats.get('default')
208
format.initialize(transport)
209
return format.open(transport)
212
def from_repository(cls, repository):
213
"""Open a cache file for a repository.
215
This will use the repository's transport to store the cache file, or
216
use the users global cache directory if the repository has no
217
transport associated with it.
219
:param repository: Repository to open the cache for
220
:return: A `BzrGitCache`
222
from ..transport.local import LocalTransport
223
repo_transport = getattr(repository, "_transport", None)
224
if (repo_transport is not None and
225
isinstance(repo_transport, LocalTransport)):
226
# Even if we don't write to this repo, we should be able
227
# to update its cache.
229
repo_transport = remove_readonly_transport_decorator(repo_transport)
230
except bzr_errors.ReadOnlyError:
234
repo_transport.mkdir('git')
235
except bzr_errors.FileExists:
237
transport = repo_transport.clone('git')
240
if transport is None:
241
transport = get_remote_cache_transport(repository)
242
return cls.from_transport(transport)
245
class CacheUpdater(object):
246
"""Base class for objects that can update a bzr-git cache."""
248
def add_object(self, obj, bzr_key_data, path):
251
:param obj: Object type ("commit", "blob" or "tree")
252
:param bzr_key_data: bzr key store data or testament_sha in case
254
:param path: Path of the object (optional)
256
raise NotImplementedError(self.add_object)
259
raise NotImplementedError(self.finish)
262
class BzrGitCache(object):
263
"""Caching backend."""
265
def __init__(self, idmap, cache_updater_klass):
267
self._cache_updater_klass = cache_updater_klass
269
def get_updater(self, rev):
270
"""Update an object that implements the CacheUpdater interface for
273
return self._cache_updater_klass(self, rev)
276
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), DictCacheUpdater)
279
class DictCacheUpdater(CacheUpdater):
280
"""Cache updater for dict-based caches."""
282
def __init__(self, cache, rev):
284
self.revid = rev.revision_id
285
self.parent_revids = rev.parent_ids
289
def add_object(self, obj, bzr_key_data, path):
290
if isinstance(obj, tuple):
291
(type_name, hexsha) = obj
293
type_name = obj.type_name
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], {})[type_data[0]] = hexsha
308
entry = (type_name, type_data)
309
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
312
if self._commit is None:
313
raise AssertionError("No commit object added")
317
class DictGitShaMap(GitShaMap):
318
"""Git SHA map that uses a dictionary."""
325
def lookup_blob_id(self, fileid, revision):
326
return self._by_fileid[revision][fileid]
328
def lookup_git_sha(self, sha):
329
for entry in self._by_sha[sha].itervalues():
332
def lookup_tree_id(self, fileid, revision):
333
return self._by_fileid[revision][fileid]
335
def lookup_commit(self, revid):
336
return self._by_revid[revid]
339
for key, entries in self._by_sha.iteritems():
340
for (type, type_data) in entries.values():
345
return self._by_sha.iterkeys()
348
class SqliteCacheUpdater(CacheUpdater):
350
def __init__(self, cache, rev):
352
self.db = self.cache.idmap.db
353
self.revid = rev.revision_id
358
def add_object(self, obj, bzr_key_data, path):
359
if isinstance(obj, tuple):
360
(type_name, hexsha) = obj
362
type_name = obj.type_name
364
if type_name == "commit":
366
if type(bzr_key_data) is not dict:
367
raise TypeError(bzr_key_data)
368
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
369
elif type_name == "tree":
370
if bzr_key_data is not None:
371
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
372
elif type_name == "blob":
373
if bzr_key_data is not None:
374
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
379
if self._commit is None:
380
raise AssertionError("No commit object added")
382
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
385
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
388
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
389
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
393
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
396
class SqliteGitCacheFormat(BzrGitCacheFormat):
398
def get_format_string(self):
399
return b'bzr-git sha map version 1 using sqlite\n'
401
def open(self, transport):
403
basepath = transport.local_abspath(".")
404
except bzr_errors.NotLocalUrl:
405
basepath = get_cache_dir()
406
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
409
class SqliteGitShaMap(GitShaMap):
410
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
412
def __init__(self, path=None):
415
self.db = sqlite3.connect(":memory:")
417
if not mapdbs().has_key(path):
418
mapdbs()[path] = sqlite3.connect(path)
419
self.db = mapdbs()[path]
420
self.db.text_factory = str
421
self.db.executescript("""
422
create table if not exists commits(
423
sha1 text not null check(length(sha1) == 40),
425
tree_sha text not null check(length(tree_sha) == 40)
427
create index if not exists commit_sha1 on commits(sha1);
428
create unique index if not exists commit_revid on commits(revid);
429
create table if not exists blobs(
430
sha1 text not null check(length(sha1) == 40),
431
fileid text not null,
434
create index if not exists blobs_sha1 on blobs(sha1);
435
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
436
create table if not exists trees(
437
sha1 text unique not null check(length(sha1) == 40),
438
fileid text not null,
441
create unique index if not exists trees_sha1 on trees(sha1);
442
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
445
self.db.executescript(
446
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
447
except sqlite3.OperationalError:
448
pass # Column already exists.
451
return "%s(%r)" % (self.__class__.__name__, self.path)
453
def lookup_commit(self, revid):
454
cursor = self.db.execute("select sha1 from commits where revid = ?",
456
row = cursor.fetchone()
461
def commit_write_group(self):
464
def lookup_blob_id(self, fileid, revision):
465
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
468
raise KeyError(fileid)
470
def lookup_tree_id(self, fileid, revision):
471
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
474
raise KeyError(fileid)
476
def lookup_git_sha(self, sha):
477
"""Lookup a Git sha in the database.
479
:param sha: Git object sha
480
:return: (type, type_data) with type_data:
481
commit: revid, tree sha, verifiers
486
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
487
for row in cursor.fetchall():
489
if row[2] is not None:
490
verifiers = {"testament3-sha1": row[2]}
493
yield ("commit", (row[0], row[1], verifiers))
494
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
495
for row in cursor.fetchall():
498
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
499
for row in cursor.fetchall():
506
"""List the revision ids known."""
507
return (row for (row,) in self.db.execute("select revid from commits"))
510
"""List the SHA1s."""
511
for table in ("blobs", "commits", "trees"):
512
for (sha,) in self.db.execute("select sha1 from %s" % table):
516
class TdbCacheUpdater(CacheUpdater):
517
"""Cache updater for tdb-based caches."""
519
def __init__(self, cache, rev):
521
self.db = cache.idmap.db
522
self.revid = rev.revision_id
523
self.parent_revids = rev.parent_ids
527
def add_object(self, obj, bzr_key_data, path):
528
if isinstance(obj, tuple):
529
(type_name, hexsha) = obj
530
sha = hex_to_sha(hexsha)
532
type_name = obj.type_name
533
sha = obj.sha().digest()
534
if type_name == "commit":
535
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
536
if type(bzr_key_data) is not dict:
537
raise TypeError(bzr_key_data)
538
type_data = (self.revid, obj.tree)
540
type_data += (bzr_key_data["testament3-sha1"],)
544
elif type_name == "blob":
545
if bzr_key_data is None:
547
self.db["\0".join(("blob", bzr_key_data[0], bzr_key_data[1]))] = sha
548
type_data = bzr_key_data
549
elif type_name == "tree":
550
if bzr_key_data is None:
552
type_data = bzr_key_data
555
entry = "\0".join((type_name, ) + type_data) + "\n"
558
oldval = self.db[key]
562
if oldval[-1] != "\n":
563
self.db[key] = "".join([oldval, "\n", entry])
565
self.db[key] = "".join([oldval, entry])
568
if self._commit is None:
569
raise AssertionError("No commit object added")
573
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
576
class TdbGitCacheFormat(BzrGitCacheFormat):
577
"""Cache format for tdb-based caches."""
579
def get_format_string(self):
580
return b'bzr-git sha map version 3 using tdb\n'
582
def open(self, transport):
584
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
585
except bzr_errors.NotLocalUrl:
586
basepath = get_cache_dir()
587
if type(basepath) is not str:
588
raise TypeError(basepath)
590
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
593
"Unable to open existing bzr-git cache because 'tdb' is not "
597
class TdbGitShaMap(GitShaMap):
598
"""SHA Map that uses a TDB database.
602
"git <sha1>" -> "<type> <type-data1> <type-data2>"
603
"commit revid" -> "<sha1> <tree-id>"
604
"tree fileid revid" -> "<sha1>"
605
"blob fileid revid" -> "<sha1>"
609
TDB_HASH_SIZE = 50000
611
def __init__(self, path=None):
617
if type(path) is not str:
618
raise TypeError(path)
619
if not mapdbs().has_key(path):
620
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
621
os.O_RDWR|os.O_CREAT)
622
self.db = mapdbs()[path]
624
if int(self.db["version"]) not in (2, 3):
625
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
626
self.db["version"], self.TDB_MAP_VERSION)
630
self.db["version"] = str(self.TDB_MAP_VERSION)
632
def start_write_group(self):
633
"""Start writing changes."""
634
self.db.transaction_start()
636
def commit_write_group(self):
637
"""Commit any pending changes."""
638
self.db.transaction_commit()
640
def abort_write_group(self):
641
"""Abort any pending changes."""
642
self.db.transaction_cancel()
645
return "%s(%r)" % (self.__class__.__name__, self.path)
647
def lookup_commit(self, revid):
649
return sha_to_hex(self.db["commit\0" + revid][:20])
651
raise KeyError("No cache entry for %r" % revid)
653
def lookup_blob_id(self, fileid, revision):
654
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
656
def lookup_git_sha(self, sha):
657
"""Lookup a Git sha in the database.
659
:param sha: Git object sha
660
:return: (type, type_data) with type_data:
661
commit: revid, tree sha
666
sha = hex_to_sha(sha)
667
value = self.db["git\0" + sha]
668
for data in value.splitlines():
669
data = data.split("\0")
670
if data[0] == "commit":
672
yield (data[0], (data[1], data[2], {}))
674
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
675
elif data[0] in ("tree", "blob"):
676
yield (data[0], tuple(data[1:]))
678
raise AssertionError("unknown type %r" % data[0])
680
def missing_revisions(self, revids):
683
if self.db.get("commit\0" + revid) is None:
688
"""List the revision ids known."""
689
for key in self.db.iterkeys():
690
if key.startswith("commit\0"):
694
"""List the SHA1s."""
695
for key in self.db.iterkeys():
696
if key.startswith("git\0"):
697
yield sha_to_hex(key[4:])
700
class VersionedFilesContentCache(ContentCache):
702
def __init__(self, vf):
706
self._vf.insert_record_stream(
707
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
708
obj.as_legacy_object_chunks())])
710
def __getitem__(self, sha):
711
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
712
entry = stream.next()
713
if entry.storage_kind == 'absent':
715
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
718
class IndexCacheUpdater(CacheUpdater):
720
def __init__(self, cache, rev):
722
self.revid = rev.revision_id
723
self.parent_revids = rev.parent_ids
727
def add_object(self, obj, bzr_key_data, path):
728
if isinstance(obj, tuple):
729
(type_name, hexsha) = obj
731
type_name = obj.type_name
733
if type_name == "commit":
735
if type(bzr_key_data) is not dict:
736
raise TypeError(bzr_key_data)
737
self.cache.idmap._add_git_sha(hexsha, "commit",
738
(self.revid, obj.tree, bzr_key_data))
739
self.cache.idmap._add_node(("commit", self.revid, "X"),
740
" ".join((hexsha, obj.tree)))
741
elif type_name == "blob":
742
self.cache.idmap._add_git_sha(hexsha, "blob", bzr_key_data)
743
self.cache.idmap._add_node(("blob", bzr_key_data[0],
744
bzr_key_data[1]), hexsha)
745
elif type_name == "tree":
746
self.cache.idmap._add_git_sha(hexsha, "tree", bzr_key_data)
754
class IndexBzrGitCache(BzrGitCache):
756
def __init__(self, transport=None):
757
mapper = versionedfile.ConstantMapper("trees")
758
shamap = IndexGitShaMap(transport.clone('index'))
759
from .transportgit import TransportObjectStore
760
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
763
class IndexGitCacheFormat(BzrGitCacheFormat):
765
def get_format_string(self):
766
return b'bzr-git sha map with git object cache version 1\n'
768
def initialize(self, transport):
769
super(IndexGitCacheFormat, self).initialize(transport)
770
transport.mkdir('index')
771
transport.mkdir('objects')
772
from .transportgit import TransportObjectStore
773
TransportObjectStore.init(transport.clone('objects'))
775
def open(self, transport):
776
return IndexBzrGitCache(transport)
779
class IndexGitShaMap(GitShaMap):
780
"""SHA Map that uses the Bazaar APIs to store a cache.
782
BTree Index file with the following contents:
784
("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
785
("commit", <revid>, "X") -> "<sha1> <tree-id>"
786
("blob", <fileid>, <revid>) -> <sha1>
790
def __init__(self, transport=None):
792
if transport is None:
793
self._transport = None
794
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
795
self._builder = self._index
798
self._transport = transport
799
self._index = _mod_index.CombinedGraphIndex([])
800
for name in self._transport.list_dir("."):
801
if not name.endswith(".rix"):
803
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
804
self._transport.stat(name).st_size)
805
self._index.insert_index(0, x)
808
def from_repository(cls, repository):
809
transport = getattr(repository, "_transport", None)
810
if transport is not None:
812
transport.mkdir('git')
813
except bzr_errors.FileExists:
815
return cls(transport.clone('git'))
816
from ..transport import get_transport
817
return cls(get_transport(get_cache_dir()))
820
if self._transport is not None:
821
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
823
return "%s()" % (self.__class__.__name__)
826
if self._builder is not None:
827
raise errors.BzrError('builder already open')
828
self.start_write_group()
829
self._builder.add_nodes(
830
((key, value) for (_, key, value) in
831
self._index.iter_all_entries()))
833
for name in self._transport.list_dir('.'):
834
if name.endswith('.rix'):
835
to_remove.append(name)
836
self.commit_write_group()
837
del self._index.indices[1:]
838
for name in to_remove:
839
self._transport.rename(name, name + '.old')
841
def start_write_group(self):
842
if self._builder is not None:
843
raise errors.BzrError('builder already open')
844
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
845
self._name = osutils.sha()
847
def commit_write_group(self):
848
if self._builder is None:
849
raise errors.BzrError('builder not open')
850
stream = self._builder.finish()
851
name = self._name.hexdigest() + ".rix"
852
size = self._transport.put_file(name, stream)
853
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
854
self._index.insert_index(0, index)
858
def abort_write_group(self):
859
if self._builder is None:
860
raise errors.BzrError('builder not open')
864
def _add_node(self, key, value):
868
self._builder.add_node(key, value)
873
def _get_entry(self, key):
874
entries = self._index.iter_entries([key])
876
return entries.next()[2]
877
except StopIteration:
878
if self._builder is None:
880
entries = self._builder.iter_entries([key])
882
return entries.next()[2]
883
except StopIteration:
886
def _iter_entries_prefix(self, prefix):
887
for entry in self._index.iter_entries_prefix([prefix]):
888
yield (entry[1], entry[2])
889
if self._builder is not None:
890
for entry in self._builder.iter_entries_prefix([prefix]):
891
yield (entry[1], entry[2])
893
def lookup_commit(self, revid):
894
return self._get_entry(("commit", revid, "X"))[:40]
896
def _add_git_sha(self, hexsha, type, type_data):
897
if hexsha is not None:
898
self._name.update(hexsha)
900
td = (type_data[0], type_data[1])
902
td += (type_data[2]["testament3-sha1"],)
907
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
909
# This object is not represented in Git - perhaps an empty
911
self._name.update(type + " ".join(type_data))
913
def lookup_blob_id(self, fileid, revision):
914
return self._get_entry(("blob", fileid, revision))
916
def lookup_git_sha(self, sha):
918
sha = sha_to_hex(sha)
919
value = self._get_entry(("git", sha, "X"))
920
data = value.split(" ", 3)
921
if data[0] == "commit":
924
verifiers = {"testament3-sha1": data[3]}
929
yield ("commit", (data[1], data[2], verifiers))
931
yield (data[0], tuple(data[1:]))
934
"""List the revision ids known."""
935
for key, value in self._iter_entries_prefix(("commit", None, None)):
938
def missing_revisions(self, revids):
939
"""Return set of all the revisions that are not present."""
940
missing_revids = set(revids)
941
for _, key, value in self._index.iter_entries((
942
("commit", revid, "X") for revid in revids)):
943
missing_revids.remove(key[1])
944
return missing_revids
947
"""List the SHA1s."""
948
for key, value in self._iter_entries_prefix(("git", None, None)):
952
formats = registry.Registry()
953
formats.register(TdbGitCacheFormat().get_format_string(),
955
formats.register(SqliteGitCacheFormat().get_format_string(),
956
SqliteGitCacheFormat())
957
formats.register(IndexGitCacheFormat().get_format_string(),
958
IndexGitCacheFormat())
959
# In the future, this will become the default:
960
formats.register('default', IndexGitCacheFormat())
964
def migrate_ancient_formats(repo_transport):
965
# Migrate older cache formats
966
repo_transport = remove_readonly_transport_decorator(repo_transport)
967
has_sqlite = repo_transport.has("git.db")
968
has_tdb = repo_transport.has("git.tdb")
969
if not has_sqlite or has_tdb:
972
repo_transport.mkdir("git")
973
except bzr_errors.FileExists:
975
# Prefer migrating git.db over git.tdb, since the latter may not
976
# be openable on some platforms.
978
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
979
repo_transport.rename("git.db", "git/idmap.db")
981
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
982
repo_transport.rename("git.tdb", "git/idmap.tdb")
985
def remove_readonly_transport_decorator(transport):
986
if transport.is_readonly():
988
return transport._decorated
989
except AttributeError:
990
raise bzr_errors.ReadOnlyError(transport)
994
def from_repository(repository):
995
"""Open a cache file for a repository.
997
If the repository is remote and there is no transport available from it
998
this will use a local file in the users cache directory
999
(typically ~/.cache/bazaar/git/)
1001
:param repository: A repository object
1003
repo_transport = getattr(repository, "_transport", None)
1004
if repo_transport is not None:
1006
migrate_ancient_formats(repo_transport)
1007
except bzr_errors.ReadOnlyError:
1008
pass # Not much we can do
1009
return BzrGitCacheFormat.from_repository(repository)