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, "bazaar", "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), 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, content_cache, cache_updater_klass):
267
self.content_cache = content_cache
268
self._cache_updater_klass = cache_updater_klass
270
def get_updater(self, rev):
271
"""Update an object that implements the CacheUpdater interface for
274
return self._cache_updater_klass(self, rev)
277
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
280
class DictCacheUpdater(CacheUpdater):
281
"""Cache updater for dict-based caches."""
283
def __init__(self, cache, rev):
285
self.revid = rev.revision_id
286
self.parent_revids = rev.parent_ids
290
def add_object(self, obj, bzr_key_data, path):
291
if obj.type_name == "commit":
293
if type(bzr_key_data) is not dict:
294
raise TypeError(bzr_key_data)
296
type_data = (self.revid, self._commit.tree, bzr_key_data)
297
self.cache.idmap._by_revid[self.revid] = obj.id
298
elif obj.type_name in ("blob", "tree"):
299
if bzr_key_data is not None:
300
if obj.type_name == "blob":
301
revision = bzr_key_data[1]
303
revision = self.revid
304
key = type_data = (bzr_key_data[0], revision)
305
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
308
entry = (obj.type_name, type_data)
309
self.cache.idmap._by_sha.setdefault(obj.id, {})[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 obj.type_name == "commit":
361
if type(bzr_key_data) is not dict:
362
raise TypeError(bzr_key_data)
363
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
364
elif obj.type_name == "tree":
365
if bzr_key_data is not None:
366
self._trees.append((obj.id, bzr_key_data[0], self.revid))
367
elif obj.type_name == "blob":
368
if bzr_key_data is not None:
369
self._blobs.append((obj.id, bzr_key_data[0], bzr_key_data[1]))
374
if self._commit is None:
375
raise AssertionError("No commit object added")
377
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
380
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
383
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
384
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
388
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
391
class SqliteGitCacheFormat(BzrGitCacheFormat):
393
def get_format_string(self):
394
return 'bzr-git sha map version 1 using sqlite\n'
396
def open(self, transport):
398
basepath = transport.local_abspath(".")
399
except bzr_errors.NotLocalUrl:
400
basepath = get_cache_dir()
401
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
404
class SqliteGitShaMap(GitShaMap):
405
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
407
def __init__(self, path=None):
410
self.db = sqlite3.connect(":memory:")
412
if not mapdbs().has_key(path):
413
mapdbs()[path] = sqlite3.connect(path)
414
self.db = mapdbs()[path]
415
self.db.text_factory = str
416
self.db.executescript("""
417
create table if not exists commits(
418
sha1 text not null check(length(sha1) == 40),
420
tree_sha text not null check(length(tree_sha) == 40)
422
create index if not exists commit_sha1 on commits(sha1);
423
create unique index if not exists commit_revid on commits(revid);
424
create table if not exists blobs(
425
sha1 text not null check(length(sha1) == 40),
426
fileid text not null,
429
create index if not exists blobs_sha1 on blobs(sha1);
430
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
431
create table if not exists trees(
432
sha1 text unique not null check(length(sha1) == 40),
433
fileid text not null,
436
create unique index if not exists trees_sha1 on trees(sha1);
437
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
440
self.db.executescript(
441
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
442
except sqlite3.OperationalError:
443
pass # Column already exists.
446
return "%s(%r)" % (self.__class__.__name__, self.path)
448
def lookup_commit(self, revid):
449
cursor = self.db.execute("select sha1 from commits where revid = ?",
451
row = cursor.fetchone()
456
def commit_write_group(self):
459
def lookup_blob_id(self, fileid, revision):
460
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
463
raise KeyError(fileid)
465
def lookup_tree_id(self, fileid, revision):
466
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
469
raise KeyError(fileid)
471
def lookup_git_sha(self, sha):
472
"""Lookup a Git sha in the database.
474
:param sha: Git object sha
475
:return: (type, type_data) with type_data:
476
commit: revid, tree sha, verifiers
481
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
482
for row in cursor.fetchall():
484
if row[2] is not None:
485
verifiers = {"testament3-sha1": row[2]}
488
yield ("commit", (row[0], row[1], verifiers))
489
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
490
for row in cursor.fetchall():
493
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
494
for row in cursor.fetchall():
501
"""List the revision ids known."""
502
return (row for (row,) in self.db.execute("select revid from commits"))
505
"""List the SHA1s."""
506
for table in ("blobs", "commits", "trees"):
507
for (sha,) in self.db.execute("select sha1 from %s" % table):
511
class TdbCacheUpdater(CacheUpdater):
512
"""Cache updater for tdb-based caches."""
514
def __init__(self, cache, rev):
516
self.db = cache.idmap.db
517
self.revid = rev.revision_id
518
self.parent_revids = rev.parent_ids
522
def add_object(self, obj, bzr_key_data, path):
523
sha = obj.sha().digest()
524
if obj.type_name == "commit":
525
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
526
if type(bzr_key_data) is not dict:
527
raise TypeError(bzr_key_data)
528
type_data = (self.revid, obj.tree)
530
type_data += (bzr_key_data["testament3-sha1"],)
534
elif obj.type_name == "blob":
535
if bzr_key_data is None:
537
self.db["\0".join(("blob", bzr_key_data[0], bzr_key_data[1]))] = sha
538
type_data = bzr_key_data
539
elif obj.type_name == "tree":
540
if bzr_key_data is None:
542
(file_id, ) = bzr_key_data
543
type_data = (file_id, self.revid)
546
entry = "\0".join((obj.type_name, ) + type_data) + "\n"
549
oldval = self.db[key]
553
if oldval[-1] != "\n":
554
self.db[key] = "".join([oldval, "\n", entry])
556
self.db[key] = "".join([oldval, entry])
559
if self._commit is None:
560
raise AssertionError("No commit object added")
564
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
567
class TdbGitCacheFormat(BzrGitCacheFormat):
568
"""Cache format for tdb-based caches."""
570
def get_format_string(self):
571
return 'bzr-git sha map version 3 using tdb\n'
573
def open(self, transport):
575
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
576
except bzr_errors.NotLocalUrl:
577
basepath = get_cache_dir()
578
if type(basepath) is not str:
579
raise TypeError(basepath)
581
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
584
"Unable to open existing bzr-git cache because 'tdb' is not "
588
class TdbGitShaMap(GitShaMap):
589
"""SHA Map that uses a TDB database.
593
"git <sha1>" -> "<type> <type-data1> <type-data2>"
594
"commit revid" -> "<sha1> <tree-id>"
595
"tree fileid revid" -> "<sha1>"
596
"blob fileid revid" -> "<sha1>"
600
TDB_HASH_SIZE = 50000
602
def __init__(self, path=None):
608
if type(path) is not str:
609
raise TypeError(path)
610
if not mapdbs().has_key(path):
611
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
612
os.O_RDWR|os.O_CREAT)
613
self.db = mapdbs()[path]
615
if int(self.db["version"]) not in (2, 3):
616
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
617
self.db["version"], self.TDB_MAP_VERSION)
621
self.db["version"] = str(self.TDB_MAP_VERSION)
623
def start_write_group(self):
624
"""Start writing changes."""
625
self.db.transaction_start()
627
def commit_write_group(self):
628
"""Commit any pending changes."""
629
self.db.transaction_commit()
631
def abort_write_group(self):
632
"""Abort any pending changes."""
633
self.db.transaction_cancel()
636
return "%s(%r)" % (self.__class__.__name__, self.path)
638
def lookup_commit(self, revid):
640
return sha_to_hex(self.db["commit\0" + revid][:20])
642
raise KeyError("No cache entry for %r" % revid)
644
def lookup_blob_id(self, fileid, revision):
645
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
647
def lookup_git_sha(self, sha):
648
"""Lookup a Git sha in the database.
650
:param sha: Git object sha
651
:return: (type, type_data) with type_data:
652
commit: revid, tree sha
657
sha = hex_to_sha(sha)
658
value = self.db["git\0" + sha]
659
for data in value.splitlines():
660
data = data.split("\0")
661
if data[0] == "commit":
663
yield (data[0], (data[1], data[2], {}))
665
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
666
elif data[0] in ("tree", "blob"):
667
yield (data[0], tuple(data[1:]))
669
raise AssertionError("unknown type %r" % data[0])
671
def missing_revisions(self, revids):
674
if self.db.get("commit\0" + revid) is None:
679
"""List the revision ids known."""
680
for key in self.db.iterkeys():
681
if key.startswith("commit\0"):
685
"""List the SHA1s."""
686
for key in self.db.iterkeys():
687
if key.startswith("git\0"):
688
yield sha_to_hex(key[4:])
691
class VersionedFilesContentCache(ContentCache):
693
def __init__(self, vf):
697
self._vf.insert_record_stream(
698
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
699
obj.as_legacy_object_chunks())])
701
def __getitem__(self, sha):
702
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
703
entry = stream.next()
704
if entry.storage_kind == 'absent':
706
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
709
class GitObjectStoreContentCache(ContentCache):
711
def __init__(self, store):
714
def add_multi(self, objs):
715
self.store.add_objects(objs)
717
def add(self, obj, path):
718
self.store.add_object(obj)
720
def __getitem__(self, sha):
721
return self.store[sha]
724
class IndexCacheUpdater(CacheUpdater):
726
def __init__(self, cache, rev):
728
self.revid = rev.revision_id
729
self.parent_revids = rev.parent_ids
732
self._cache_objs = set()
734
def add_object(self, obj, bzr_key_data, path):
735
if obj.type_name == "commit":
737
if type(bzr_key_data) is not dict:
738
raise TypeError(bzr_key_data)
739
self.cache.idmap._add_git_sha(obj.id, "commit",
740
(self.revid, obj.tree, bzr_key_data))
741
self.cache.idmap._add_node(("commit", self.revid, "X"),
742
" ".join((obj.id, obj.tree)))
743
self._cache_objs.add((obj, path))
744
elif obj.type_name == "blob":
745
self.cache.idmap._add_git_sha(obj.id, "blob", bzr_key_data)
746
self.cache.idmap._add_node(("blob", bzr_key_data[0],
747
bzr_key_data[1]), obj.id)
748
elif obj.type_name == "tree":
749
self.cache.idmap._add_git_sha(obj.id, "tree",
750
(bzr_key_data[0], self.revid))
751
self._cache_objs.add((obj, path))
756
self.cache.content_cache.add_multi(self._cache_objs)
760
class IndexBzrGitCache(BzrGitCache):
762
def __init__(self, transport=None):
763
mapper = versionedfile.ConstantMapper("trees")
764
shamap = IndexGitShaMap(transport.clone('index'))
765
#trees_store = knit.make_file_factory(True, mapper)(transport)
766
#content_cache = VersionedFilesContentCache(trees_store)
767
from .transportgit import TransportObjectStore
768
store = TransportObjectStore(transport.clone('objects'))
769
content_cache = GitObjectStoreContentCache(store)
770
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
774
class IndexGitCacheFormat(BzrGitCacheFormat):
776
def get_format_string(self):
777
return 'bzr-git sha map with git object cache version 1\n'
779
def initialize(self, transport):
780
super(IndexGitCacheFormat, self).initialize(transport)
781
transport.mkdir('index')
782
transport.mkdir('objects')
783
from .transportgit import TransportObjectStore
784
TransportObjectStore.init(transport.clone('objects'))
786
def open(self, transport):
787
return IndexBzrGitCache(transport)
790
class IndexGitShaMap(GitShaMap):
791
"""SHA Map that uses the Bazaar APIs to store a cache.
793
BTree Index file with the following contents:
795
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
796
("commit", <revid>) -> "<sha1> <tree-id>"
797
("blob", <fileid>, <revid>) -> <sha1>
801
def __init__(self, transport=None):
802
if transport is None:
803
self._transport = None
804
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
805
self._builder = self._index
808
self._transport = transport
809
self._index = _mod_index.CombinedGraphIndex([])
810
for name in self._transport.list_dir("."):
811
if not name.endswith(".rix"):
813
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
814
self._transport.stat(name).st_size)
815
self._index.insert_index(0, x)
818
def from_repository(cls, repository):
819
transport = getattr(repository, "_transport", None)
820
if transport is not None:
822
transport.mkdir('git')
823
except bzr_errors.FileExists:
825
return cls(transport.clone('git'))
826
from ...transport import get_transport
827
return cls(get_transport(get_cache_dir()))
830
if self._transport is not None:
831
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
833
return "%s()" % (self.__class__.__name__)
836
if self._builder is not None:
837
raise errors.BzrError('builder already open')
838
self.start_write_group()
839
for _, key, value in self._index.iter_all_entries():
840
self._builder.add_node(key, value)
842
for name in self._transport.list_dir('.'):
843
if name.endswith('.rix'):
844
to_remove.append(name)
845
self.commit_write_group()
846
del self._index.indices[1:]
847
for name in to_remove:
848
self._transport.rename(name, name + '.old')
850
def start_write_group(self):
851
if self._builder is not None:
852
raise errors.BzrError('builder already open')
853
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
854
self._name = osutils.sha()
856
def commit_write_group(self):
857
if self._builder is None:
858
raise errors.BzrError('builder not open')
859
stream = self._builder.finish()
860
name = self._name.hexdigest() + ".rix"
861
size = self._transport.put_file(name, stream)
862
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
863
self._index.insert_index(0, index)
867
def abort_write_group(self):
868
if self._builder is None:
869
raise errors.BzrError('builder not open')
873
def _add_node(self, key, value):
875
self._builder.add_node(key, value)
876
except bzr_errors.BadIndexDuplicateKey:
877
# Multiple bzr objects can have the same contents
882
def _get_entry(self, key):
883
entries = self._index.iter_entries([key])
885
return entries.next()[2]
886
except StopIteration:
887
if self._builder is None:
889
entries = self._builder.iter_entries([key])
891
return entries.next()[2]
892
except StopIteration:
895
def _iter_entries_prefix(self, prefix):
896
for entry in self._index.iter_entries_prefix([prefix]):
897
yield (entry[1], entry[2])
898
if self._builder is not None:
899
for entry in self._builder.iter_entries_prefix([prefix]):
900
yield (entry[1], entry[2])
902
def lookup_commit(self, revid):
903
return self._get_entry(("commit", revid, "X"))[:40]
905
def _add_git_sha(self, hexsha, type, type_data):
906
if hexsha is not None:
907
self._name.update(hexsha)
909
td = (type_data[0], type_data[1])
911
td += (type_data[2]["testament3-sha1"],)
916
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
918
# This object is not represented in Git - perhaps an empty
920
self._name.update(type + " ".join(type_data))
922
def lookup_blob_id(self, fileid, revision):
923
return self._get_entry(("blob", fileid, revision))
925
def lookup_git_sha(self, sha):
927
sha = sha_to_hex(sha)
929
for key, value in self._iter_entries_prefix(("git", sha, None)):
931
data = value.split(" ", 3)
932
if data[0] == "commit":
934
verifiers = {"testament3-sha1": data[3]}
937
yield ("commit", (data[1], data[2], verifiers))
939
yield (data[0], tuple(data[1:]))
944
"""List the revision ids known."""
945
for key, value in self._iter_entries_prefix(("commit", None, None)):
948
def missing_revisions(self, revids):
949
"""Return set of all the revisions that are not present."""
950
missing_revids = set(revids)
951
for _, key, value in self._index.iter_entries((
952
("commit", revid, "X") for revid in revids)):
953
missing_revids.remove(key[1])
954
return missing_revids
957
"""List the SHA1s."""
958
for key, value in self._iter_entries_prefix(("git", None, None)):
962
formats = registry.Registry()
963
formats.register(TdbGitCacheFormat().get_format_string(),
965
formats.register(SqliteGitCacheFormat().get_format_string(),
966
SqliteGitCacheFormat())
967
formats.register(IndexGitCacheFormat().get_format_string(),
968
IndexGitCacheFormat())
969
# In the future, this will become the default:
970
# formats.register('default', IndexGitCacheFormat())
974
formats.register('default', SqliteGitCacheFormat())
976
formats.register('default', TdbGitCacheFormat())
980
def migrate_ancient_formats(repo_transport):
981
# Migrate older cache formats
982
repo_transport = remove_readonly_transport_decorator(repo_transport)
983
has_sqlite = repo_transport.has("git.db")
984
has_tdb = repo_transport.has("git.tdb")
985
if not has_sqlite or has_tdb:
988
repo_transport.mkdir("git")
989
except bzr_errors.FileExists:
991
# Prefer migrating git.db over git.tdb, since the latter may not
992
# be openable on some platforms.
994
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
995
repo_transport.rename("git.db", "git/idmap.db")
997
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
998
repo_transport.rename("git.tdb", "git/idmap.tdb")
1001
def remove_readonly_transport_decorator(transport):
1002
if transport.is_readonly():
1004
return transport._decorated
1005
except AttributeError:
1006
raise bzr_errors.ReadOnlyError(transport)
1010
def from_repository(repository):
1011
"""Open a cache file for a repository.
1013
If the repository is remote and there is no transport available from it
1014
this will use a local file in the users cache directory
1015
(typically ~/.cache/bazaar/git/)
1017
:param repository: A repository object
1019
repo_transport = getattr(repository, "_transport", None)
1020
if repo_transport is not None:
1022
migrate_ancient_formats(repo_transport)
1023
except bzr_errors.ReadOnlyError:
1024
pass # Not much we can do
1025
return BzrGitCacheFormat.from_repository(repository)