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 path not in mapdbs():
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.decode('ascii')
533
sha = obj.sha().digest()
534
if type_name == "commit":
535
self.db[b"commit\0" + self.revid] = b"\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[b"\0".join((b"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 = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
558
oldval = self.db[key]
562
if not oldval.endswith(b'\n'):
563
self.db[key] = b"".join([oldval, b"\n", entry])
565
self.db[key] = b"".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 path not in mapdbs():
618
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
619
os.O_RDWR|os.O_CREAT)
620
self.db = mapdbs()[path]
622
if int(self.db[b"version"]) not in (2, 3):
623
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
624
self.db[b"version"], self.TDB_MAP_VERSION)
628
self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
630
def start_write_group(self):
631
"""Start writing changes."""
632
self.db.transaction_start()
634
def commit_write_group(self):
635
"""Commit any pending changes."""
636
self.db.transaction_commit()
638
def abort_write_group(self):
639
"""Abort any pending changes."""
640
self.db.transaction_cancel()
643
return "%s(%r)" % (self.__class__.__name__, self.path)
645
def lookup_commit(self, revid):
647
return sha_to_hex(self.db[b"commit\0" + revid][:20])
649
raise KeyError("No cache entry for %r" % revid)
651
def lookup_blob_id(self, fileid, revision):
652
return sha_to_hex(self.db[b"\0".join(("blob", fileid, revision))])
654
def lookup_git_sha(self, sha):
655
"""Lookup a Git sha in the database.
657
:param sha: Git object sha
658
:return: (type, type_data) with type_data:
659
commit: revid, tree sha
664
sha = hex_to_sha(sha)
665
value = self.db[b"git\0" + sha]
666
for data in value.splitlines():
667
data = data.split(b"\0")
668
type_name = data[0].decode('ascii')
669
if type_name == "commit":
671
yield (type_name, (data[1], data[2], {}))
673
yield (type_name, (data[1], data[2], {"testament3-sha1": data[3]}))
674
elif type_name in ("tree", "blob"):
675
yield (type_name, tuple(data[1:]))
677
raise AssertionError("unknown type %r" % type_name)
679
def missing_revisions(self, revids):
682
if self.db.get(b"commit\0" + revid) is None:
687
"""List the revision ids known."""
688
for key in self.db.iterkeys():
689
if key.startswith(b"commit\0"):
693
"""List the SHA1s."""
694
for key in self.db.iterkeys():
695
if key.startswith(b"git\0"):
696
yield sha_to_hex(key[4:])
699
class VersionedFilesContentCache(ContentCache):
701
def __init__(self, vf):
705
self._vf.insert_record_stream(
706
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
707
obj.as_legacy_object_chunks())])
709
def __getitem__(self, sha):
710
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
712
if entry.storage_kind == 'absent':
714
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
717
class IndexCacheUpdater(CacheUpdater):
719
def __init__(self, cache, rev):
721
self.revid = rev.revision_id
722
self.parent_revids = rev.parent_ids
726
def add_object(self, obj, bzr_key_data, path):
727
if isinstance(obj, tuple):
728
(type_name, hexsha) = obj
730
type_name = obj.type_name
732
if type_name == "commit":
734
if type(bzr_key_data) is not dict:
735
raise TypeError(bzr_key_data)
736
self.cache.idmap._add_git_sha(hexsha, "commit",
737
(self.revid, obj.tree, bzr_key_data))
738
self.cache.idmap._add_node(("commit", self.revid, "X"),
739
" ".join((hexsha, obj.tree)))
740
elif type_name == "blob":
741
self.cache.idmap._add_git_sha(hexsha, "blob", bzr_key_data)
742
self.cache.idmap._add_node(("blob", bzr_key_data[0],
743
bzr_key_data[1]), hexsha)
744
elif type_name == "tree":
745
self.cache.idmap._add_git_sha(hexsha, "tree", bzr_key_data)
753
class IndexBzrGitCache(BzrGitCache):
755
def __init__(self, transport=None):
756
mapper = versionedfile.ConstantMapper("trees")
757
shamap = IndexGitShaMap(transport.clone('index'))
758
from .transportgit import TransportObjectStore
759
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
762
class IndexGitCacheFormat(BzrGitCacheFormat):
764
def get_format_string(self):
765
return b'bzr-git sha map with git object cache version 1\n'
767
def initialize(self, transport):
768
super(IndexGitCacheFormat, self).initialize(transport)
769
transport.mkdir('index')
770
transport.mkdir('objects')
771
from .transportgit import TransportObjectStore
772
TransportObjectStore.init(transport.clone('objects'))
774
def open(self, transport):
775
return IndexBzrGitCache(transport)
778
class IndexGitShaMap(GitShaMap):
779
"""SHA Map that uses the Bazaar APIs to store a cache.
781
BTree Index file with the following contents:
783
("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
784
("commit", <revid>, "X") -> "<sha1> <tree-id>"
785
("blob", <fileid>, <revid>) -> <sha1>
789
def __init__(self, transport=None):
791
if transport is None:
792
self._transport = None
793
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
794
self._builder = self._index
797
self._transport = transport
798
self._index = _mod_index.CombinedGraphIndex([])
799
for name in self._transport.list_dir("."):
800
if not name.endswith(".rix"):
802
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
803
self._transport.stat(name).st_size)
804
self._index.insert_index(0, x)
807
def from_repository(cls, repository):
808
transport = getattr(repository, "_transport", None)
809
if transport is not None:
811
transport.mkdir('git')
812
except bzr_errors.FileExists:
814
return cls(transport.clone('git'))
815
from ...transport import get_transport
816
return cls(get_transport(get_cache_dir()))
819
if self._transport is not None:
820
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
822
return "%s()" % (self.__class__.__name__)
825
if self._builder is not None:
826
raise errors.BzrError('builder already open')
827
self.start_write_group()
828
self._builder.add_nodes(
829
((key, value) for (_, key, value) in
830
self._index.iter_all_entries()))
832
for name in self._transport.list_dir('.'):
833
if name.endswith('.rix'):
834
to_remove.append(name)
835
self.commit_write_group()
836
del self._index.indices[1:]
837
for name in to_remove:
838
self._transport.rename(name, name + '.old')
840
def start_write_group(self):
841
if self._builder is not None:
842
raise errors.BzrError('builder already open')
843
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
844
self._name = osutils.sha()
846
def commit_write_group(self):
847
if self._builder is None:
848
raise errors.BzrError('builder not open')
849
stream = self._builder.finish()
850
name = self._name.hexdigest() + ".rix"
851
size = self._transport.put_file(name, stream)
852
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
853
self._index.insert_index(0, index)
857
def abort_write_group(self):
858
if self._builder is None:
859
raise errors.BzrError('builder not open')
863
def _add_node(self, key, value):
867
self._builder.add_node(key, value)
872
def _get_entry(self, key):
873
entries = self._index.iter_entries([key])
875
return next(entries)[2]
876
except StopIteration:
877
if self._builder is None:
879
entries = self._builder.iter_entries([key])
881
return next(entries)[2]
882
except StopIteration:
885
def _iter_entries_prefix(self, prefix):
886
for entry in self._index.iter_entries_prefix([prefix]):
887
yield (entry[1], entry[2])
888
if self._builder is not None:
889
for entry in self._builder.iter_entries_prefix([prefix]):
890
yield (entry[1], entry[2])
892
def lookup_commit(self, revid):
893
return self._get_entry(("commit", revid, "X"))[:40]
895
def _add_git_sha(self, hexsha, type, type_data):
896
if hexsha is not None:
897
self._name.update(hexsha)
899
td = (type_data[0], type_data[1])
901
td += (type_data[2]["testament3-sha1"],)
906
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
908
# This object is not represented in Git - perhaps an empty
910
self._name.update(type + " ".join(type_data))
912
def lookup_blob_id(self, fileid, revision):
913
return self._get_entry(("blob", fileid, revision))
915
def lookup_git_sha(self, sha):
917
sha = sha_to_hex(sha)
918
value = self._get_entry(("git", sha, "X"))
919
data = value.split(" ", 3)
920
if data[0] == "commit":
923
verifiers = {"testament3-sha1": data[3]}
928
yield ("commit", (data[1], data[2], verifiers))
930
yield (data[0], tuple(data[1:]))
933
"""List the revision ids known."""
934
for key, value in self._iter_entries_prefix(("commit", None, None)):
937
def missing_revisions(self, revids):
938
"""Return set of all the revisions that are not present."""
939
missing_revids = set(revids)
940
for _, key, value in self._index.iter_entries((
941
("commit", revid, "X") for revid in revids)):
942
missing_revids.remove(key[1])
943
return missing_revids
946
"""List the SHA1s."""
947
for key, value in self._iter_entries_prefix(("git", None, None)):
951
formats = registry.Registry()
952
formats.register(TdbGitCacheFormat().get_format_string(),
954
formats.register(SqliteGitCacheFormat().get_format_string(),
955
SqliteGitCacheFormat())
956
formats.register(IndexGitCacheFormat().get_format_string(),
957
IndexGitCacheFormat())
958
# In the future, this will become the default:
959
formats.register('default', IndexGitCacheFormat())
963
def migrate_ancient_formats(repo_transport):
964
# Migrate older cache formats
965
repo_transport = remove_readonly_transport_decorator(repo_transport)
966
has_sqlite = repo_transport.has("git.db")
967
has_tdb = repo_transport.has("git.tdb")
968
if not has_sqlite or has_tdb:
971
repo_transport.mkdir("git")
972
except bzr_errors.FileExists:
974
# Prefer migrating git.db over git.tdb, since the latter may not
975
# be openable on some platforms.
977
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
978
repo_transport.rename("git.db", "git/idmap.db")
980
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
981
repo_transport.rename("git.tdb", "git/idmap.tdb")
984
def remove_readonly_transport_decorator(transport):
985
if transport.is_readonly():
987
return transport._decorated
988
except AttributeError:
989
raise bzr_errors.ReadOnlyError(transport)
993
def from_repository(repository):
994
"""Open a cache file for a repository.
996
If the repository is remote and there is no transport available from it
997
this will use a local file in the users cache directory
998
(typically ~/.cache/bazaar/git/)
1000
:param repository: A repository object
1002
repo_transport = getattr(repository, "_transport", None)
1003
if repo_transport is not None:
1005
migrate_ancient_formats(repo_transport)
1006
except bzr_errors.ReadOnlyError:
1007
pass # Not much we can do
1008
return BzrGitCacheFormat.from_repository(repository)