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 or
85
(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")
93
check_pysqlite_version(sqlite3)
94
except (ImportError, bzr_errors.BzrError) as e:
95
from pysqlite2 import dbapi2 as sqlite3
96
check_pysqlite_version(sqlite3)
98
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
100
raise bzr_errors.BzrError("missing sqlite library")
103
_mapdbs = threading.local()
105
"""Get a cache for this thread's db connections."""
108
except AttributeError:
113
class GitShaMap(object):
114
"""Git<->Bzr revision id mapping database."""
116
def lookup_git_sha(self, sha):
117
"""Lookup a Git sha in the database.
118
:param sha: Git object sha
119
:return: list with (type, type_data) tuples with type_data:
120
commit: revid, tree_sha, verifiers
124
raise NotImplementedError(self.lookup_git_sha)
126
def lookup_blob_id(self, file_id, revision):
127
"""Retrieve a Git blob SHA by file id.
129
:param file_id: File id of the file/symlink
130
:param revision: revision in which the file was last changed.
132
raise NotImplementedError(self.lookup_blob_id)
134
def lookup_tree_id(self, file_id, revision):
135
"""Retrieve a Git tree SHA by file id.
137
raise NotImplementedError(self.lookup_tree_id)
139
def lookup_commit(self, revid):
140
"""Retrieve a Git commit SHA by Bazaar revision id.
142
raise NotImplementedError(self.lookup_commit)
145
"""List the revision ids known."""
146
raise NotImplementedError(self.revids)
148
def missing_revisions(self, revids):
149
"""Return set of all the revisions that are not present."""
150
present_revids = set(self.revids())
151
if not isinstance(revids, set):
153
return revids - present_revids
156
"""List the SHA1s."""
157
raise NotImplementedError(self.sha1s)
159
def start_write_group(self):
160
"""Start writing changes."""
162
def commit_write_group(self):
163
"""Commit any pending changes."""
165
def abort_write_group(self):
166
"""Abort any pending changes."""
169
class ContentCache(object):
170
"""Object that can cache Git objects."""
172
def add(self, object):
174
raise NotImplementedError(self.add)
176
def add_multi(self, objects):
177
"""Add multiple objects."""
181
def __getitem__(self, sha):
182
"""Retrieve an item, by SHA."""
183
raise NotImplementedError(self.__getitem__)
186
class BzrGitCacheFormat(object):
187
"""Bazaar-Git Cache Format."""
189
def get_format_string(self):
190
"""Return a single-line unique format string for this cache format."""
191
raise NotImplementedError(self.get_format_string)
193
def open(self, transport):
194
"""Open this format on a transport."""
195
raise NotImplementedError(self.open)
197
def initialize(self, transport):
198
"""Create a new instance of this cache format at transport."""
199
transport.put_bytes('format', self.get_format_string())
202
def from_transport(self, transport):
203
"""Open a cache file present on a transport, or initialize one.
205
:param transport: Transport to use
206
:return: A BzrGitCache instance
209
format_name = transport.get_bytes('format')
210
format = formats.get(format_name)
211
except bzr_errors.NoSuchFile:
212
format = formats.get('default')
213
format.initialize(transport)
214
return format.open(transport)
217
def from_repository(cls, repository):
218
"""Open a cache file for a repository.
220
This will use the repository's transport to store the cache file, or
221
use the users global cache directory if the repository has no
222
transport associated with it.
224
:param repository: Repository to open the cache for
225
:return: A `BzrGitCache`
227
from ..transport.local import LocalTransport
228
repo_transport = getattr(repository, "_transport", None)
229
if (repo_transport is not None and
230
isinstance(repo_transport, LocalTransport)):
231
# Even if we don't write to this repo, we should be able
232
# to update its cache.
234
repo_transport = remove_readonly_transport_decorator(repo_transport)
235
except bzr_errors.ReadOnlyError:
239
repo_transport.mkdir('git')
240
except bzr_errors.FileExists:
242
transport = repo_transport.clone('git')
245
if transport is None:
246
transport = get_remote_cache_transport(repository)
247
return cls.from_transport(transport)
250
class CacheUpdater(object):
251
"""Base class for objects that can update a bzr-git cache."""
253
def add_object(self, obj, bzr_key_data, path):
256
:param obj: Object type ("commit", "blob" or "tree")
257
:param bzr_key_data: bzr key store data or testament_sha in case
259
:param path: Path of the object (optional)
261
raise NotImplementedError(self.add_object)
264
raise NotImplementedError(self.finish)
267
class BzrGitCache(object):
268
"""Caching backend."""
270
def __init__(self, idmap, cache_updater_klass):
272
self._cache_updater_klass = cache_updater_klass
274
def get_updater(self, rev):
275
"""Update an object that implements the CacheUpdater interface for
278
return self._cache_updater_klass(self, rev)
281
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), DictCacheUpdater)
284
class DictCacheUpdater(CacheUpdater):
285
"""Cache updater for dict-based caches."""
287
def __init__(self, cache, rev):
289
self.revid = rev.revision_id
290
self.parent_revids = rev.parent_ids
294
def add_object(self, obj, bzr_key_data, path):
295
if isinstance(obj, tuple):
296
(type_name, hexsha) = obj
298
type_name = obj.type_name.decode('ascii')
300
if not isinstance(hexsha, bytes):
301
raise TypeError(hexsha)
302
if type_name == "commit":
304
if type(bzr_key_data) is not dict:
305
raise TypeError(bzr_key_data)
307
type_data = (self.revid, self._commit.tree, bzr_key_data)
308
self.cache.idmap._by_revid[self.revid] = hexsha
309
elif type_name in ("blob", "tree"):
310
if bzr_key_data is not None:
311
key = type_data = bzr_key_data
312
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = hexsha
315
entry = (type_name, type_data)
316
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
319
if self._commit is None:
320
raise AssertionError("No commit object added")
324
class DictGitShaMap(GitShaMap):
325
"""Git SHA map that uses a dictionary."""
332
def lookup_blob_id(self, fileid, revision):
333
return self._by_fileid[revision][fileid]
335
def lookup_git_sha(self, sha):
336
if not isinstance(sha, bytes):
338
for entry in viewvalues(self._by_sha[sha]):
341
def lookup_tree_id(self, fileid, revision):
342
return self._by_fileid[revision][fileid]
344
def lookup_commit(self, revid):
345
return self._by_revid[revid]
348
for key, entries in viewitems(self._by_sha):
349
for (type, type_data) in viewvalues(entries):
354
return viewkeys(self._by_sha)
357
class SqliteCacheUpdater(CacheUpdater):
359
def __init__(self, cache, rev):
361
self.db = self.cache.idmap.db
362
self.revid = rev.revision_id
367
def add_object(self, obj, bzr_key_data, path):
368
if isinstance(obj, tuple):
369
(type_name, hexsha) = obj
371
type_name = obj.type_name.decode('ascii')
373
if not isinstance(hexsha, bytes):
374
raise TypeError(hexsha)
375
if type_name == "commit":
377
if type(bzr_key_data) is not dict:
378
raise TypeError(bzr_key_data)
379
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
380
elif type_name == "tree":
381
if bzr_key_data is not None:
382
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
383
elif type_name == "blob":
384
if bzr_key_data is not None:
385
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
390
if self._commit is None:
391
raise AssertionError("No commit object added")
393
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
396
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
399
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
400
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
404
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
407
class SqliteGitCacheFormat(BzrGitCacheFormat):
409
def get_format_string(self):
410
return b'bzr-git sha map version 1 using sqlite\n'
412
def open(self, transport):
414
basepath = transport.local_abspath(".")
415
except bzr_errors.NotLocalUrl:
416
basepath = get_cache_dir()
417
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
420
class SqliteGitShaMap(GitShaMap):
421
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
423
def __init__(self, path=None):
426
self.db = sqlite3.connect(":memory:")
428
if path not in mapdbs():
429
mapdbs()[path] = sqlite3.connect(path)
430
self.db = mapdbs()[path]
431
self.db.text_factory = str
432
self.db.executescript("""
433
create table if not exists commits(
434
sha1 text not null check(length(sha1) == 40),
436
tree_sha text not null check(length(tree_sha) == 40)
438
create index if not exists commit_sha1 on commits(sha1);
439
create unique index if not exists commit_revid on commits(revid);
440
create table if not exists blobs(
441
sha1 text not null check(length(sha1) == 40),
442
fileid text not null,
445
create index if not exists blobs_sha1 on blobs(sha1);
446
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
447
create table if not exists trees(
448
sha1 text unique not null check(length(sha1) == 40),
449
fileid text not null,
452
create unique index if not exists trees_sha1 on trees(sha1);
453
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
456
self.db.executescript(
457
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
458
except sqlite3.OperationalError:
459
pass # Column already exists.
462
return "%s(%r)" % (self.__class__.__name__, self.path)
464
def lookup_commit(self, revid):
465
cursor = self.db.execute("select sha1 from commits where revid = ?",
467
row = cursor.fetchone()
472
def commit_write_group(self):
475
def lookup_blob_id(self, fileid, revision):
476
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
479
raise KeyError(fileid)
481
def lookup_tree_id(self, fileid, revision):
482
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
485
raise KeyError(fileid)
487
def lookup_git_sha(self, sha):
488
"""Lookup a Git sha in the database.
490
:param sha: Git object sha
491
:return: (type, type_data) with type_data:
492
commit: revid, tree sha, verifiers
497
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
498
for row in cursor.fetchall():
500
if row[2] is not None:
501
verifiers = {"testament3-sha1": row[2]}
504
yield ("commit", (row[0], row[1], verifiers))
505
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
506
for row in cursor.fetchall():
509
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
510
for row in cursor.fetchall():
517
"""List the revision ids known."""
518
return (row for (row,) in self.db.execute("select revid from commits"))
521
"""List the SHA1s."""
522
for table in ("blobs", "commits", "trees"):
523
for (sha,) in self.db.execute("select sha1 from %s" % table):
524
yield sha.encode('ascii')
527
class TdbCacheUpdater(CacheUpdater):
528
"""Cache updater for tdb-based caches."""
530
def __init__(self, cache, rev):
532
self.db = cache.idmap.db
533
self.revid = rev.revision_id
534
self.parent_revids = rev.parent_ids
538
def add_object(self, obj, bzr_key_data, path):
539
if isinstance(obj, tuple):
540
(type_name, hexsha) = obj
541
sha = hex_to_sha(hexsha)
543
type_name = obj.type_name.decode('ascii')
544
sha = obj.sha().digest()
545
if type_name == "commit":
546
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
547
if type(bzr_key_data) is not dict:
548
raise TypeError(bzr_key_data)
549
type_data = (self.revid, obj.tree)
551
type_data += (bzr_key_data["testament3-sha1"],)
555
elif type_name == "blob":
556
if bzr_key_data is None:
558
self.db[b"\0".join((b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
559
type_data = bzr_key_data
560
elif type_name == "tree":
561
if bzr_key_data is None:
563
type_data = bzr_key_data
566
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
569
oldval = self.db[key]
573
if not oldval.endswith(b'\n'):
574
self.db[key] = b"".join([oldval, b"\n", entry])
576
self.db[key] = b"".join([oldval, entry])
579
if self._commit is None:
580
raise AssertionError("No commit object added")
584
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
587
class TdbGitCacheFormat(BzrGitCacheFormat):
588
"""Cache format for tdb-based caches."""
590
def get_format_string(self):
591
return b'bzr-git sha map version 3 using tdb\n'
593
def open(self, transport):
595
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
596
except bzr_errors.NotLocalUrl:
597
basepath = get_cache_dir()
598
if not isinstance(basepath, str):
599
raise TypeError(basepath)
601
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
604
"Unable to open existing bzr-git cache because 'tdb' is not "
608
class TdbGitShaMap(GitShaMap):
609
"""SHA Map that uses a TDB database.
613
"git <sha1>" -> "<type> <type-data1> <type-data2>"
614
"commit revid" -> "<sha1> <tree-id>"
615
"tree fileid revid" -> "<sha1>"
616
"blob fileid revid" -> "<sha1>"
620
TDB_HASH_SIZE = 50000
622
def __init__(self, path=None):
628
if path not in mapdbs():
629
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
630
os.O_RDWR|os.O_CREAT)
631
self.db = mapdbs()[path]
633
if int(self.db[b"version"]) not in (2, 3):
634
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
635
self.db[b"version"], self.TDB_MAP_VERSION)
639
self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
641
def start_write_group(self):
642
"""Start writing changes."""
643
self.db.transaction_start()
645
def commit_write_group(self):
646
"""Commit any pending changes."""
647
self.db.transaction_commit()
649
def abort_write_group(self):
650
"""Abort any pending changes."""
651
self.db.transaction_cancel()
654
return "%s(%r)" % (self.__class__.__name__, self.path)
656
def lookup_commit(self, revid):
658
return sha_to_hex(self.db[b"commit\0" + revid][:20])
660
raise KeyError("No cache entry for %r" % revid)
662
def lookup_blob_id(self, fileid, revision):
663
return sha_to_hex(self.db[b"\0".join((b"blob", fileid, revision))])
665
def lookup_git_sha(self, sha):
666
"""Lookup a Git sha in the database.
668
:param sha: Git object sha
669
:return: (type, type_data) with type_data:
670
commit: revid, tree sha
675
sha = hex_to_sha(sha)
676
value = self.db[b"git\0" + sha]
677
for data in value.splitlines():
678
data = data.split(b"\0")
679
type_name = data[0].decode('ascii')
680
if type_name == "commit":
682
yield (type_name, (data[1], data[2], {}))
684
yield (type_name, (data[1], data[2], {"testament3-sha1": data[3]}))
685
elif type_name in ("tree", "blob"):
686
yield (type_name, tuple(data[1:]))
688
raise AssertionError("unknown type %r" % type_name)
690
def missing_revisions(self, revids):
693
if self.db.get(b"commit\0" + revid) is None:
699
return self.db.keys()
700
except AttributeError: # python < 3
701
return self.db.iterkeys()
704
"""List the revision ids known."""
705
for key in self._keys():
706
if key.startswith(b"commit\0"):
710
"""List the SHA1s."""
711
for key in self._keys():
712
if key.startswith(b"git\0"):
713
yield sha_to_hex(key[4:])
716
class VersionedFilesContentCache(ContentCache):
718
def __init__(self, vf):
722
self._vf.insert_record_stream(
723
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
724
obj.as_legacy_object_chunks())])
726
def __getitem__(self, sha):
727
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
729
if entry.storage_kind == 'absent':
731
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
734
class IndexCacheUpdater(CacheUpdater):
736
def __init__(self, cache, rev):
738
self.revid = rev.revision_id
739
self.parent_revids = rev.parent_ids
743
def add_object(self, obj, bzr_key_data, path):
744
if isinstance(obj, tuple):
745
(type_name, hexsha) = obj
747
type_name = obj.type_name.decode('ascii')
749
if type_name == "commit":
751
if type(bzr_key_data) is not dict:
752
raise TypeError(bzr_key_data)
753
self.cache.idmap._add_git_sha(hexsha, b"commit",
754
(self.revid, obj.tree, bzr_key_data))
755
self.cache.idmap._add_node((b"commit", self.revid, b"X"),
756
b" ".join((hexsha, obj.tree)))
757
elif type_name == "blob":
758
self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
759
self.cache.idmap._add_node((b"blob", bzr_key_data[0],
760
bzr_key_data[1]), hexsha)
761
elif type_name == "tree":
762
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
770
class IndexBzrGitCache(BzrGitCache):
772
def __init__(self, transport=None):
773
mapper = versionedfile.ConstantMapper("trees")
774
shamap = IndexGitShaMap(transport.clone('index'))
775
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
778
class IndexGitCacheFormat(BzrGitCacheFormat):
780
def get_format_string(self):
781
return b'bzr-git sha map with git object cache version 1\n'
783
def initialize(self, transport):
784
super(IndexGitCacheFormat, self).initialize(transport)
785
transport.mkdir('index')
786
transport.mkdir('objects')
787
from .transportgit import TransportObjectStore
788
TransportObjectStore.init(transport.clone('objects'))
790
def open(self, transport):
791
return IndexBzrGitCache(transport)
794
class IndexGitShaMap(GitShaMap):
795
"""SHA Map that uses the Bazaar APIs to store a cache.
797
BTree Index file with the following contents:
799
("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
800
("commit", <revid>, "X") -> "<sha1> <tree-id>"
801
("blob", <fileid>, <revid>) -> <sha1>
805
def __init__(self, transport=None):
807
if transport is None:
808
self._transport = None
809
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
810
self._builder = self._index
813
self._transport = transport
814
self._index = _mod_index.CombinedGraphIndex([])
815
for name in self._transport.list_dir("."):
816
if not name.endswith(".rix"):
818
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
819
self._transport.stat(name).st_size)
820
self._index.insert_index(0, x)
823
def from_repository(cls, repository):
824
transport = getattr(repository, "_transport", None)
825
if transport is not None:
827
transport.mkdir('git')
828
except bzr_errors.FileExists:
830
return cls(transport.clone('git'))
831
from ..transport import get_transport
832
return cls(get_transport(get_cache_dir()))
835
if self._transport is not None:
836
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
838
return "%s()" % (self.__class__.__name__)
841
if self._builder is not None:
842
raise errors.BzrError('builder already open')
843
self.start_write_group()
844
self._builder.add_nodes(
845
((key, value) for (_, key, value) in
846
self._index.iter_all_entries()))
848
for name in self._transport.list_dir('.'):
849
if name.endswith('.rix'):
850
to_remove.append(name)
851
self.commit_write_group()
852
del self._index.indices[1:]
853
for name in to_remove:
854
self._transport.rename(name, name + '.old')
856
def start_write_group(self):
857
if self._builder is not None:
858
raise errors.BzrError('builder already open')
859
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
860
self._name = osutils.sha()
862
def commit_write_group(self):
863
if self._builder is None:
864
raise errors.BzrError('builder not open')
865
stream = self._builder.finish()
866
name = self._name.hexdigest() + ".rix"
867
size = self._transport.put_file(name, stream)
868
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
869
self._index.insert_index(0, index)
873
def abort_write_group(self):
874
if self._builder is None:
875
raise errors.BzrError('builder not open')
879
def _add_node(self, key, value):
883
self._builder.add_node(key, value)
888
def _get_entry(self, key):
889
entries = self._index.iter_entries([key])
891
return next(entries)[2]
892
except StopIteration:
893
if self._builder is None:
895
entries = self._builder.iter_entries([key])
897
return next(entries)[2]
898
except StopIteration:
901
def _iter_entries_prefix(self, prefix):
902
for entry in self._index.iter_entries_prefix([prefix]):
903
yield (entry[1], entry[2])
904
if self._builder is not None:
905
for entry in self._builder.iter_entries_prefix([prefix]):
906
yield (entry[1], entry[2])
908
def lookup_commit(self, revid):
909
return self._get_entry((b"commit", revid, b"X"))[:40]
911
def _add_git_sha(self, hexsha, type, type_data):
912
if hexsha is not None:
913
self._name.update(hexsha)
914
if type == b"commit":
915
td = (type_data[0], type_data[1])
917
td += (type_data[2]["testament3-sha1"],)
922
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
924
# This object is not represented in Git - perhaps an empty
926
self._name.update(type + b" ".join(type_data))
928
def lookup_blob_id(self, fileid, revision):
929
return self._get_entry((b"blob", fileid, revision))
931
def lookup_git_sha(self, sha):
933
sha = sha_to_hex(sha)
934
value = self._get_entry((b"git", sha, b"X"))
935
data = value.split(b" ", 3)
936
if data[0] == b"commit":
939
verifiers = {"testament3-sha1": data[3]}
944
yield ("commit", (data[1], data[2], verifiers))
946
yield (data[0].decode('ascii'), tuple(data[1:]))
949
"""List the revision ids known."""
950
for key, value in self._iter_entries_prefix((b"commit", None, None)):
953
def missing_revisions(self, revids):
954
"""Return set of all the revisions that are not present."""
955
missing_revids = set(revids)
956
for _, key, value in self._index.iter_entries((
957
(b"commit", revid, b"X") for revid in revids)):
958
missing_revids.remove(key[1])
959
return missing_revids
962
"""List the SHA1s."""
963
for key, value in self._iter_entries_prefix((b"git", None, None)):
967
formats = registry.Registry()
968
formats.register(TdbGitCacheFormat().get_format_string(),
970
formats.register(SqliteGitCacheFormat().get_format_string(),
971
SqliteGitCacheFormat())
972
formats.register(IndexGitCacheFormat().get_format_string(),
973
IndexGitCacheFormat())
974
# In the future, this will become the default:
975
formats.register('default', IndexGitCacheFormat())
979
def migrate_ancient_formats(repo_transport):
980
# Migrate older cache formats
981
repo_transport = remove_readonly_transport_decorator(repo_transport)
982
has_sqlite = repo_transport.has("git.db")
983
has_tdb = repo_transport.has("git.tdb")
984
if not has_sqlite or has_tdb:
987
repo_transport.mkdir("git")
988
except bzr_errors.FileExists:
990
# Prefer migrating git.db over git.tdb, since the latter may not
991
# be openable on some platforms.
993
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
994
repo_transport.rename("git.db", "git/idmap.db")
996
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
997
repo_transport.rename("git.tdb", "git/idmap.tdb")
1000
def remove_readonly_transport_decorator(transport):
1001
if transport.is_readonly():
1003
return transport._decorated
1004
except AttributeError:
1005
raise bzr_errors.ReadOnlyError(transport)
1009
def from_repository(repository):
1010
"""Open a cache file for a repository.
1012
If the repository is remote and there is no transport available from it
1013
this will use a local file in the users cache directory
1014
(typically ~/.cache/bazaar/git/)
1016
:param repository: A repository object
1018
repo_transport = getattr(repository, "_transport", None)
1019
if repo_transport is not None:
1021
migrate_ancient_formats(repo_transport)
1022
except bzr_errors.ReadOnlyError:
1023
pass # Not much we can do
1024
return BzrGitCacheFormat.from_repository(repository)