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 ..sixish import (
48
from ..transport import (
55
from xdg.BaseDirectory import xdg_cache_home
57
from ..config import config_dir
58
ret = os.path.join(config_dir(), "git")
60
ret = os.path.join(xdg_cache_home, "breezy", "git")
61
if not os.path.isdir(ret):
66
def get_remote_cache_transport(repository):
67
"""Retrieve the transport to use when accessing (unwritable) remote
70
uuid = getattr(repository, "uuid", None)
72
path = get_cache_dir()
74
path = os.path.join(get_cache_dir(), uuid)
75
if not os.path.isdir(path):
77
return get_transport(path)
80
def check_pysqlite_version(sqlite3):
81
"""Check that sqlite library is compatible.
84
if (sqlite3.sqlite_version_info[0] < 3
85
or (sqlite3.sqlite_version_info[0] == 3 and
86
sqlite3.sqlite_version_info[1] < 3)):
87
trace.warning('Needs at least sqlite 3.3.x')
88
raise bzr_errors.BzrError("incompatible sqlite library")
94
check_pysqlite_version(sqlite3)
95
except (ImportError, bzr_errors.BzrError):
96
from pysqlite2 import dbapi2 as sqlite3
97
check_pysqlite_version(sqlite3)
99
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
101
raise bzr_errors.BzrError("missing sqlite library")
104
_mapdbs = threading.local()
108
"""Get a cache for this thread's db connections."""
111
except AttributeError:
116
class GitShaMap(object):
117
"""Git<->Bzr revision id mapping database."""
119
def lookup_git_sha(self, sha):
120
"""Lookup a Git sha in the database.
121
:param sha: Git object sha
122
:return: list with (type, type_data) tuples with type_data:
123
commit: revid, tree_sha, verifiers
127
raise NotImplementedError(self.lookup_git_sha)
129
def lookup_blob_id(self, file_id, revision):
130
"""Retrieve a Git blob SHA by file id.
132
:param file_id: File id of the file/symlink
133
:param revision: revision in which the file was last changed.
135
raise NotImplementedError(self.lookup_blob_id)
137
def lookup_tree_id(self, file_id, revision):
138
"""Retrieve a Git tree SHA by file id.
140
raise NotImplementedError(self.lookup_tree_id)
142
def lookup_commit(self, revid):
143
"""Retrieve a Git commit SHA by Bazaar revision id.
145
raise NotImplementedError(self.lookup_commit)
148
"""List the revision ids known."""
149
raise NotImplementedError(self.revids)
151
def missing_revisions(self, revids):
152
"""Return set of all the revisions that are not present."""
153
present_revids = set(self.revids())
154
if not isinstance(revids, set):
156
return revids - present_revids
159
"""List the SHA1s."""
160
raise NotImplementedError(self.sha1s)
162
def start_write_group(self):
163
"""Start writing changes."""
165
def commit_write_group(self):
166
"""Commit any pending changes."""
168
def abort_write_group(self):
169
"""Abort any pending changes."""
172
class ContentCache(object):
173
"""Object that can cache Git objects."""
175
def add(self, object):
177
raise NotImplementedError(self.add)
179
def add_multi(self, objects):
180
"""Add multiple objects."""
184
def __getitem__(self, sha):
185
"""Retrieve an item, by SHA."""
186
raise NotImplementedError(self.__getitem__)
189
class BzrGitCacheFormat(object):
190
"""Bazaar-Git Cache Format."""
192
def get_format_string(self):
193
"""Return a single-line unique format string for this cache format."""
194
raise NotImplementedError(self.get_format_string)
196
def open(self, transport):
197
"""Open this format on a transport."""
198
raise NotImplementedError(self.open)
200
def initialize(self, transport):
201
"""Create a new instance of this cache format at transport."""
202
transport.put_bytes('format', self.get_format_string())
205
def from_transport(self, transport):
206
"""Open a cache file present on a transport, or initialize one.
208
:param transport: Transport to use
209
:return: A BzrGitCache instance
212
format_name = transport.get_bytes('format')
213
format = formats.get(format_name)
214
except bzr_errors.NoSuchFile:
215
format = formats.get('default')
216
format.initialize(transport)
217
return format.open(transport)
220
def from_repository(cls, repository):
221
"""Open a cache file for a repository.
223
This will use the repository's transport to store the cache file, or
224
use the users global cache directory if the repository has no
225
transport associated with it.
227
:param repository: Repository to open the cache for
228
:return: A `BzrGitCache`
230
from ..transport.local import LocalTransport
231
repo_transport = getattr(repository, "_transport", None)
232
if (repo_transport is not None
233
and isinstance(repo_transport, LocalTransport)):
234
# Even if we don't write to this repo, we should be able
235
# to update its cache.
237
repo_transport = remove_readonly_transport_decorator(
239
except bzr_errors.ReadOnlyError:
243
repo_transport.mkdir('git')
244
except bzr_errors.FileExists:
246
transport = repo_transport.clone('git')
249
if transport is None:
250
transport = get_remote_cache_transport(repository)
251
return cls.from_transport(transport)
254
class CacheUpdater(object):
255
"""Base class for objects that can update a bzr-git cache."""
257
def add_object(self, obj, bzr_key_data, path):
260
:param obj: Object type ("commit", "blob" or "tree")
261
:param bzr_key_data: bzr key store data or testament_sha in case
263
:param path: Path of the object (optional)
265
raise NotImplementedError(self.add_object)
268
raise NotImplementedError(self.finish)
271
class BzrGitCache(object):
272
"""Caching backend."""
274
def __init__(self, idmap, cache_updater_klass):
276
self._cache_updater_klass = cache_updater_klass
278
def get_updater(self, rev):
279
"""Update an object that implements the CacheUpdater interface for
282
return self._cache_updater_klass(self, rev)
285
def DictBzrGitCache():
286
return BzrGitCache(DictGitShaMap(), DictCacheUpdater)
289
class DictCacheUpdater(CacheUpdater):
290
"""Cache updater for dict-based caches."""
292
def __init__(self, cache, rev):
294
self.revid = rev.revision_id
295
self.parent_revids = rev.parent_ids
299
def add_object(self, obj, bzr_key_data, path):
300
if isinstance(obj, tuple):
301
(type_name, hexsha) = obj
303
type_name = obj.type_name.decode('ascii')
305
if not isinstance(hexsha, bytes):
306
raise TypeError(hexsha)
307
if type_name == "commit":
309
if type(bzr_key_data) is not dict:
310
raise TypeError(bzr_key_data)
312
type_data = (self.revid, self._commit.tree, bzr_key_data)
313
self.cache.idmap._by_revid[self.revid] = hexsha
314
elif type_name in ("blob", "tree"):
315
if bzr_key_data is not None:
316
key = type_data = bzr_key_data
317
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
318
type_data[0]] = hexsha
321
entry = (type_name, type_data)
322
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
325
if self._commit is None:
326
raise AssertionError("No commit object added")
330
class DictGitShaMap(GitShaMap):
331
"""Git SHA map that uses a dictionary."""
338
def lookup_blob_id(self, fileid, revision):
339
return self._by_fileid[revision][fileid]
341
def lookup_git_sha(self, sha):
342
if not isinstance(sha, bytes):
344
for entry in viewvalues(self._by_sha[sha]):
347
def lookup_tree_id(self, fileid, revision):
348
return self._by_fileid[revision][fileid]
350
def lookup_commit(self, revid):
351
return self._by_revid[revid]
354
for key, entries in viewitems(self._by_sha):
355
for (type, type_data) in viewvalues(entries):
360
return viewkeys(self._by_sha)
363
class SqliteCacheUpdater(CacheUpdater):
365
def __init__(self, cache, rev):
367
self.db = self.cache.idmap.db
368
self.revid = rev.revision_id
373
def add_object(self, obj, bzr_key_data, path):
374
if isinstance(obj, tuple):
375
(type_name, hexsha) = obj
377
type_name = obj.type_name.decode('ascii')
379
if not isinstance(hexsha, bytes):
380
raise TypeError(hexsha)
381
if type_name == "commit":
383
if type(bzr_key_data) is not dict:
384
raise TypeError(bzr_key_data)
385
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
386
elif type_name == "tree":
387
if bzr_key_data is not None:
388
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
389
elif type_name == "blob":
390
if bzr_key_data is not None:
391
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
396
if self._commit is None:
397
raise AssertionError("No commit object added")
399
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
402
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
405
"replace into commits (sha1, revid, tree_sha, testament3_sha1) "
406
"values (?, ?, ?, ?)",
407
(self._commit.id, self.revid, self._commit.tree,
408
self._testament3_sha1))
412
def SqliteBzrGitCache(p):
413
return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
416
class SqliteGitCacheFormat(BzrGitCacheFormat):
418
def get_format_string(self):
419
return b'bzr-git sha map version 1 using sqlite\n'
421
def open(self, transport):
423
basepath = transport.local_abspath(".")
424
except bzr_errors.NotLocalUrl:
425
basepath = get_cache_dir()
426
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
429
class SqliteGitShaMap(GitShaMap):
430
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
432
def __init__(self, path=None):
435
self.db = sqlite3.connect(":memory:")
437
if path not in mapdbs():
438
mapdbs()[path] = sqlite3.connect(path)
439
self.db = mapdbs()[path]
440
self.db.text_factory = str
441
self.db.executescript("""
442
create table if not exists commits(
443
sha1 text not null check(length(sha1) == 40),
445
tree_sha text not null check(length(tree_sha) == 40)
447
create index if not exists commit_sha1 on commits(sha1);
448
create unique index if not exists commit_revid on commits(revid);
449
create table if not exists blobs(
450
sha1 text not null check(length(sha1) == 40),
451
fileid text not null,
454
create index if not exists blobs_sha1 on blobs(sha1);
455
create unique index if not exists blobs_fileid_revid on blobs(
457
create table if not exists trees(
458
sha1 text unique not null check(length(sha1) == 40),
459
fileid text not null,
462
create unique index if not exists trees_sha1 on trees(sha1);
463
create unique index if not exists trees_fileid_revid on trees(
467
self.db.executescript(
468
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
469
except sqlite3.OperationalError:
470
pass # Column already exists.
473
return "%s(%r)" % (self.__class__.__name__, self.path)
475
def lookup_commit(self, revid):
476
cursor = self.db.execute("select sha1 from commits where revid = ?",
478
row = cursor.fetchone()
483
def commit_write_group(self):
486
def lookup_blob_id(self, fileid, revision):
487
row = self.db.execute(
488
"select sha1 from blobs where fileid = ? and revid = ?",
489
(fileid, revision)).fetchone()
492
raise KeyError(fileid)
494
def lookup_tree_id(self, fileid, revision):
495
row = self.db.execute(
496
"select sha1 from trees where fileid = ? and revid = ?",
497
(fileid, revision)).fetchone()
500
raise KeyError(fileid)
502
def lookup_git_sha(self, sha):
503
"""Lookup a Git sha in the database.
505
:param sha: Git object sha
506
:return: (type, type_data) with type_data:
507
commit: revid, tree sha, verifiers
512
cursor = self.db.execute(
513
"select revid, tree_sha, testament3_sha1 from commits where "
515
for row in cursor.fetchall():
517
if row[2] is not None:
518
verifiers = {"testament3-sha1": row[2]}
521
yield ("commit", (row[0], row[1], verifiers))
522
cursor = self.db.execute(
523
"select fileid, revid from blobs where sha1 = ?", (sha,))
524
for row in cursor.fetchall():
527
cursor = self.db.execute(
528
"select fileid, revid from trees where sha1 = ?", (sha,))
529
for row in cursor.fetchall():
536
"""List the revision ids known."""
537
return (row for (row,) in self.db.execute("select revid from commits"))
540
"""List the SHA1s."""
541
for table in ("blobs", "commits", "trees"):
542
for (sha,) in self.db.execute("select sha1 from %s" % table):
543
yield sha.encode('ascii')
546
class TdbCacheUpdater(CacheUpdater):
547
"""Cache updater for tdb-based caches."""
549
def __init__(self, cache, rev):
551
self.db = cache.idmap.db
552
self.revid = rev.revision_id
553
self.parent_revids = rev.parent_ids
557
def add_object(self, obj, bzr_key_data, path):
558
if isinstance(obj, tuple):
559
(type_name, hexsha) = obj
560
sha = hex_to_sha(hexsha)
562
type_name = obj.type_name.decode('ascii')
563
sha = obj.sha().digest()
564
if type_name == "commit":
565
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
566
if type(bzr_key_data) is not dict:
567
raise TypeError(bzr_key_data)
568
type_data = (self.revid, obj.tree)
570
type_data += (bzr_key_data["testament3-sha1"],)
574
elif type_name == "blob":
575
if bzr_key_data is None:
578
(b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
579
type_data = bzr_key_data
580
elif type_name == "tree":
581
if bzr_key_data is None:
583
type_data = bzr_key_data
586
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
589
oldval = self.db[key]
593
if not oldval.endswith(b'\n'):
594
self.db[key] = b"".join([oldval, b"\n", entry])
596
self.db[key] = b"".join([oldval, entry])
599
if self._commit is None:
600
raise AssertionError("No commit object added")
604
def TdbBzrGitCache(p):
605
return BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
608
class TdbGitCacheFormat(BzrGitCacheFormat):
609
"""Cache format for tdb-based caches."""
611
def get_format_string(self):
612
return b'bzr-git sha map version 3 using tdb\n'
614
def open(self, transport):
616
basepath = transport.local_abspath(".")
617
except bzr_errors.NotLocalUrl:
618
basepath = get_cache_dir()
620
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
623
"Unable to open existing bzr-git cache because 'tdb' is not "
627
class TdbGitShaMap(GitShaMap):
628
"""SHA Map that uses a TDB database.
632
"git <sha1>" -> "<type> <type-data1> <type-data2>"
633
"commit revid" -> "<sha1> <tree-id>"
634
"tree fileid revid" -> "<sha1>"
635
"blob fileid revid" -> "<sha1>"
639
TDB_HASH_SIZE = 50000
641
def __init__(self, path=None):
647
if path not in mapdbs():
648
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
649
os.O_RDWR | os.O_CREAT)
650
self.db = mapdbs()[path]
652
if int(self.db[b"version"]) not in (2, 3):
654
"SHA Map is incompatible (%s -> %d), rebuilding database.",
655
self.db[b"version"], self.TDB_MAP_VERSION)
659
self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
661
def start_write_group(self):
662
"""Start writing changes."""
663
self.db.transaction_start()
665
def commit_write_group(self):
666
"""Commit any pending changes."""
667
self.db.transaction_commit()
669
def abort_write_group(self):
670
"""Abort any pending changes."""
671
self.db.transaction_cancel()
674
return "%s(%r)" % (self.__class__.__name__, self.path)
676
def lookup_commit(self, revid):
678
return sha_to_hex(self.db[b"commit\0" + revid][:20])
680
raise KeyError("No cache entry for %r" % revid)
682
def lookup_blob_id(self, fileid, revision):
683
return sha_to_hex(self.db[b"\0".join((b"blob", fileid, revision))])
685
def lookup_git_sha(self, sha):
686
"""Lookup a Git sha in the database.
688
:param sha: Git object sha
689
:return: (type, type_data) with type_data:
690
commit: revid, tree sha
695
sha = hex_to_sha(sha)
696
value = self.db[b"git\0" + sha]
697
for data in value.splitlines():
698
data = data.split(b"\0")
699
type_name = data[0].decode('ascii')
700
if type_name == "commit":
702
yield (type_name, (data[1], data[2], {}))
704
yield (type_name, (data[1], data[2],
705
{"testament3-sha1": data[3]}))
706
elif type_name in ("tree", "blob"):
707
yield (type_name, tuple(data[1:]))
709
raise AssertionError("unknown type %r" % type_name)
711
def missing_revisions(self, revids):
714
if self.db.get(b"commit\0" + revid) is None:
720
return self.db.keys()
721
except AttributeError: # python < 3
722
return self.db.iterkeys()
725
"""List the revision ids known."""
726
for key in self._keys():
727
if key.startswith(b"commit\0"):
731
"""List the SHA1s."""
732
for key in self._keys():
733
if key.startswith(b"git\0"):
734
yield sha_to_hex(key[4:])
737
class VersionedFilesContentCache(ContentCache):
739
def __init__(self, vf):
743
self._vf.insert_record_stream(
744
[versionedfile.ChunkedContentFactory(
745
(obj.id,), [], None, obj.as_legacy_object_chunks())])
747
def __getitem__(self, sha):
748
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
750
if entry.storage_kind == 'absent':
752
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
755
class IndexCacheUpdater(CacheUpdater):
757
def __init__(self, cache, rev):
759
self.revid = rev.revision_id
760
self.parent_revids = rev.parent_ids
764
def add_object(self, obj, bzr_key_data, path):
765
if isinstance(obj, tuple):
766
(type_name, hexsha) = obj
768
type_name = obj.type_name.decode('ascii')
770
if type_name == "commit":
772
if type(bzr_key_data) is not dict:
773
raise TypeError(bzr_key_data)
774
self.cache.idmap._add_git_sha(hexsha, b"commit",
775
(self.revid, obj.tree, bzr_key_data))
776
self.cache.idmap._add_node((b"commit", self.revid, b"X"),
777
b" ".join((hexsha, obj.tree)))
778
elif type_name == "blob":
779
self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
780
self.cache.idmap._add_node((b"blob", bzr_key_data[0],
781
bzr_key_data[1]), hexsha)
782
elif type_name == "tree":
783
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
791
class IndexBzrGitCache(BzrGitCache):
793
def __init__(self, transport=None):
794
shamap = IndexGitShaMap(transport.clone('index'))
795
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
798
class IndexGitCacheFormat(BzrGitCacheFormat):
800
def get_format_string(self):
801
return b'bzr-git sha map with git object cache version 1\n'
803
def initialize(self, transport):
804
super(IndexGitCacheFormat, self).initialize(transport)
805
transport.mkdir('index')
806
transport.mkdir('objects')
807
from .transportgit import TransportObjectStore
808
TransportObjectStore.init(transport.clone('objects'))
810
def open(self, transport):
811
return IndexBzrGitCache(transport)
814
class IndexGitShaMap(GitShaMap):
815
"""SHA Map that uses the Bazaar APIs to store a cache.
817
BTree Index file with the following contents:
819
("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
820
("commit", <revid>, "X") -> "<sha1> <tree-id>"
821
("blob", <fileid>, <revid>) -> <sha1>
825
def __init__(self, transport=None):
827
if transport is None:
828
self._transport = None
829
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
830
self._builder = self._index
833
self._transport = transport
834
self._index = _mod_index.CombinedGraphIndex([])
835
for name in self._transport.list_dir("."):
836
if not name.endswith(".rix"):
838
x = _mod_btree_index.BTreeGraphIndex(
839
self._transport, name, self._transport.stat(name).st_size)
840
self._index.insert_index(0, x)
843
def from_repository(cls, repository):
844
transport = getattr(repository, "_transport", None)
845
if transport is not None:
847
transport.mkdir('git')
848
except bzr_errors.FileExists:
850
return cls(transport.clone('git'))
851
from ..transport import get_transport
852
return cls(get_transport(get_cache_dir()))
855
if self._transport is not None:
856
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
858
return "%s()" % (self.__class__.__name__)
861
if self._builder is not None:
862
raise bzr_errors.BzrError('builder already open')
863
self.start_write_group()
864
self._builder.add_nodes(
865
((key, value) for (_, key, value) in
866
self._index.iter_all_entries()))
868
for name in self._transport.list_dir('.'):
869
if name.endswith('.rix'):
870
to_remove.append(name)
871
self.commit_write_group()
872
del self._index.indices[1:]
873
for name in to_remove:
874
self._transport.rename(name, name + '.old')
876
def start_write_group(self):
877
if self._builder is not None:
878
raise bzr_errors.BzrError('builder already open')
879
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
880
self._name = osutils.sha()
882
def commit_write_group(self):
883
if self._builder is None:
884
raise bzr_errors.BzrError('builder not open')
885
stream = self._builder.finish()
886
name = self._name.hexdigest() + ".rix"
887
size = self._transport.put_file(name, stream)
888
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
889
self._index.insert_index(0, index)
893
def abort_write_group(self):
894
if self._builder is None:
895
raise bzr_errors.BzrError('builder not open')
899
def _add_node(self, key, value):
903
self._builder.add_node(key, value)
908
def _get_entry(self, key):
909
entries = self._index.iter_entries([key])
911
return next(entries)[2]
912
except StopIteration:
913
if self._builder is None:
915
entries = self._builder.iter_entries([key])
917
return next(entries)[2]
918
except StopIteration:
921
def _iter_entries_prefix(self, prefix):
922
for entry in self._index.iter_entries_prefix([prefix]):
923
yield (entry[1], entry[2])
924
if self._builder is not None:
925
for entry in self._builder.iter_entries_prefix([prefix]):
926
yield (entry[1], entry[2])
928
def lookup_commit(self, revid):
929
return self._get_entry((b"commit", revid, b"X"))[:40]
931
def _add_git_sha(self, hexsha, type, type_data):
932
if hexsha is not None:
933
self._name.update(hexsha)
934
if type == b"commit":
935
td = (type_data[0], type_data[1])
937
td += (type_data[2]["testament3-sha1"],)
942
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
944
# This object is not represented in Git - perhaps an empty
946
self._name.update(type + b" ".join(type_data))
948
def lookup_blob_id(self, fileid, revision):
949
return self._get_entry((b"blob", fileid, revision))
951
def lookup_git_sha(self, sha):
953
sha = sha_to_hex(sha)
954
value = self._get_entry((b"git", sha, b"X"))
955
data = value.split(b" ", 3)
956
if data[0] == b"commit":
959
verifiers = {"testament3-sha1": data[3]}
964
yield ("commit", (data[1], data[2], verifiers))
966
yield (data[0].decode('ascii'), tuple(data[1:]))
969
"""List the revision ids known."""
970
for key, value in self._iter_entries_prefix((b"commit", None, None)):
973
def missing_revisions(self, revids):
974
"""Return set of all the revisions that are not present."""
975
missing_revids = set(revids)
976
for _, key, value in self._index.iter_entries((
977
(b"commit", revid, b"X") for revid in revids)):
978
missing_revids.remove(key[1])
979
return missing_revids
982
"""List the SHA1s."""
983
for key, value in self._iter_entries_prefix((b"git", None, None)):
987
formats = registry.Registry()
988
formats.register(TdbGitCacheFormat().get_format_string(),
990
formats.register(SqliteGitCacheFormat().get_format_string(),
991
SqliteGitCacheFormat())
992
formats.register(IndexGitCacheFormat().get_format_string(),
993
IndexGitCacheFormat())
994
# In the future, this will become the default:
995
formats.register('default', IndexGitCacheFormat())
998
def migrate_ancient_formats(repo_transport):
999
# Migrate older cache formats
1000
repo_transport = remove_readonly_transport_decorator(repo_transport)
1001
has_sqlite = repo_transport.has("git.db")
1002
has_tdb = repo_transport.has("git.tdb")
1003
if not has_sqlite or has_tdb:
1006
repo_transport.mkdir("git")
1007
except bzr_errors.FileExists:
1009
# Prefer migrating git.db over git.tdb, since the latter may not
1010
# be openable on some platforms.
1012
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
1013
repo_transport.rename("git.db", "git/idmap.db")
1015
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
1016
repo_transport.rename("git.tdb", "git/idmap.tdb")
1019
def remove_readonly_transport_decorator(transport):
1020
if transport.is_readonly():
1022
return transport._decorated
1023
except AttributeError:
1024
raise bzr_errors.ReadOnlyError(transport)
1028
def from_repository(repository):
1029
"""Open a cache file for a repository.
1031
If the repository is remote and there is no transport available from it
1032
this will use a local file in the users cache directory
1033
(typically ~/.cache/bazaar/git/)
1035
:param repository: A repository object
1037
repo_transport = getattr(repository, "_transport", None)
1038
if repo_transport is not None:
1040
migrate_ancient_formats(repo_transport)
1041
except bzr_errors.ReadOnlyError:
1042
pass # Not much we can do
1043
return BzrGitCacheFormat.from_repository(repository)