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) as e:
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(): return BzrGitCache(DictGitShaMap(), DictCacheUpdater)
288
class DictCacheUpdater(CacheUpdater):
289
"""Cache updater for dict-based caches."""
291
def __init__(self, cache, rev):
293
self.revid = rev.revision_id
294
self.parent_revids = rev.parent_ids
298
def add_object(self, obj, bzr_key_data, path):
299
if isinstance(obj, tuple):
300
(type_name, hexsha) = obj
302
type_name = obj.type_name.decode('ascii')
304
if not isinstance(hexsha, bytes):
305
raise TypeError(hexsha)
306
if type_name == "commit":
308
if type(bzr_key_data) is not dict:
309
raise TypeError(bzr_key_data)
311
type_data = (self.revid, self._commit.tree, bzr_key_data)
312
self.cache.idmap._by_revid[self.revid] = hexsha
313
elif type_name in ("blob", "tree"):
314
if bzr_key_data is not None:
315
key = type_data = bzr_key_data
316
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
317
type_data[0]] = hexsha
320
entry = (type_name, type_data)
321
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
324
if self._commit is None:
325
raise AssertionError("No commit object added")
329
class DictGitShaMap(GitShaMap):
330
"""Git SHA map that uses a dictionary."""
337
def lookup_blob_id(self, fileid, revision):
338
return self._by_fileid[revision][fileid]
340
def lookup_git_sha(self, sha):
341
if not isinstance(sha, bytes):
343
for entry in viewvalues(self._by_sha[sha]):
346
def lookup_tree_id(self, fileid, revision):
347
return self._by_fileid[revision][fileid]
349
def lookup_commit(self, revid):
350
return self._by_revid[revid]
353
for key, entries in viewitems(self._by_sha):
354
for (type, type_data) in viewvalues(entries):
359
return viewkeys(self._by_sha)
362
class SqliteCacheUpdater(CacheUpdater):
364
def __init__(self, cache, rev):
366
self.db = self.cache.idmap.db
367
self.revid = rev.revision_id
372
def add_object(self, obj, bzr_key_data, path):
373
if isinstance(obj, tuple):
374
(type_name, hexsha) = obj
376
type_name = obj.type_name.decode('ascii')
378
if not isinstance(hexsha, bytes):
379
raise TypeError(hexsha)
380
if type_name == "commit":
382
if type(bzr_key_data) is not dict:
383
raise TypeError(bzr_key_data)
384
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
385
elif type_name == "tree":
386
if bzr_key_data is not None:
387
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
388
elif type_name == "blob":
389
if bzr_key_data is not None:
390
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
395
if self._commit is None:
396
raise AssertionError("No commit object added")
398
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
401
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
404
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
405
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
409
def SqliteBzrGitCache(p): return BzrGitCache(
410
SqliteGitShaMap(p), SqliteCacheUpdater)
413
class SqliteGitCacheFormat(BzrGitCacheFormat):
415
def get_format_string(self):
416
return b'bzr-git sha map version 1 using sqlite\n'
418
def open(self, transport):
420
basepath = transport.local_abspath(".")
421
except bzr_errors.NotLocalUrl:
422
basepath = get_cache_dir()
423
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
426
class SqliteGitShaMap(GitShaMap):
427
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
429
def __init__(self, path=None):
432
self.db = sqlite3.connect(":memory:")
434
if path not in mapdbs():
435
mapdbs()[path] = sqlite3.connect(path)
436
self.db = mapdbs()[path]
437
self.db.text_factory = str
438
self.db.executescript("""
439
create table if not exists commits(
440
sha1 text not null check(length(sha1) == 40),
442
tree_sha text not null check(length(tree_sha) == 40)
444
create index if not exists commit_sha1 on commits(sha1);
445
create unique index if not exists commit_revid on commits(revid);
446
create table if not exists blobs(
447
sha1 text not null check(length(sha1) == 40),
448
fileid text not null,
451
create index if not exists blobs_sha1 on blobs(sha1);
452
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
453
create table if not exists trees(
454
sha1 text unique not null check(length(sha1) == 40),
455
fileid text not null,
458
create unique index if not exists trees_sha1 on trees(sha1);
459
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
462
self.db.executescript(
463
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
464
except sqlite3.OperationalError:
465
pass # Column already exists.
468
return "%s(%r)" % (self.__class__.__name__, self.path)
470
def lookup_commit(self, revid):
471
cursor = self.db.execute("select sha1 from commits where revid = ?",
473
row = cursor.fetchone()
478
def commit_write_group(self):
481
def lookup_blob_id(self, fileid, revision):
482
row = self.db.execute(
483
"select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
486
raise KeyError(fileid)
488
def lookup_tree_id(self, fileid, revision):
489
row = self.db.execute(
490
"select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
493
raise KeyError(fileid)
495
def lookup_git_sha(self, sha):
496
"""Lookup a Git sha in the database.
498
:param sha: Git object sha
499
:return: (type, type_data) with type_data:
500
commit: revid, tree sha, verifiers
505
cursor = self.db.execute(
506
"select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
507
for row in cursor.fetchall():
509
if row[2] is not None:
510
verifiers = {"testament3-sha1": row[2]}
513
yield ("commit", (row[0], row[1], verifiers))
514
cursor = self.db.execute(
515
"select fileid, revid from blobs where sha1 = ?", (sha,))
516
for row in cursor.fetchall():
519
cursor = self.db.execute(
520
"select fileid, revid from trees where sha1 = ?", (sha,))
521
for row in cursor.fetchall():
528
"""List the revision ids known."""
529
return (row for (row,) in self.db.execute("select revid from commits"))
532
"""List the SHA1s."""
533
for table in ("blobs", "commits", "trees"):
534
for (sha,) in self.db.execute("select sha1 from %s" % table):
535
yield sha.encode('ascii')
538
class TdbCacheUpdater(CacheUpdater):
539
"""Cache updater for tdb-based caches."""
541
def __init__(self, cache, rev):
543
self.db = cache.idmap.db
544
self.revid = rev.revision_id
545
self.parent_revids = rev.parent_ids
549
def add_object(self, obj, bzr_key_data, path):
550
if isinstance(obj, tuple):
551
(type_name, hexsha) = obj
552
sha = hex_to_sha(hexsha)
554
type_name = obj.type_name.decode('ascii')
555
sha = obj.sha().digest()
556
if type_name == "commit":
557
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
558
if type(bzr_key_data) is not dict:
559
raise TypeError(bzr_key_data)
560
type_data = (self.revid, obj.tree)
562
type_data += (bzr_key_data["testament3-sha1"],)
566
elif type_name == "blob":
567
if bzr_key_data is None:
570
(b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
571
type_data = bzr_key_data
572
elif type_name == "tree":
573
if bzr_key_data is None:
575
type_data = bzr_key_data
578
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
581
oldval = self.db[key]
585
if not oldval.endswith(b'\n'):
586
self.db[key] = b"".join([oldval, b"\n", entry])
588
self.db[key] = b"".join([oldval, entry])
591
if self._commit is None:
592
raise AssertionError("No commit object added")
596
def TdbBzrGitCache(p): return BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
599
class TdbGitCacheFormat(BzrGitCacheFormat):
600
"""Cache format for tdb-based caches."""
602
def get_format_string(self):
603
return b'bzr-git sha map version 3 using tdb\n'
605
def open(self, transport):
607
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
608
except bzr_errors.NotLocalUrl:
609
basepath = get_cache_dir()
610
if not isinstance(basepath, str):
611
raise TypeError(basepath)
613
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
616
"Unable to open existing bzr-git cache because 'tdb' is not "
620
class TdbGitShaMap(GitShaMap):
621
"""SHA Map that uses a TDB database.
625
"git <sha1>" -> "<type> <type-data1> <type-data2>"
626
"commit revid" -> "<sha1> <tree-id>"
627
"tree fileid revid" -> "<sha1>"
628
"blob fileid revid" -> "<sha1>"
632
TDB_HASH_SIZE = 50000
634
def __init__(self, path=None):
640
if path not in mapdbs():
641
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
642
os.O_RDWR | os.O_CREAT)
643
self.db = mapdbs()[path]
645
if int(self.db[b"version"]) not in (2, 3):
646
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
647
self.db[b"version"], self.TDB_MAP_VERSION)
651
self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
653
def start_write_group(self):
654
"""Start writing changes."""
655
self.db.transaction_start()
657
def commit_write_group(self):
658
"""Commit any pending changes."""
659
self.db.transaction_commit()
661
def abort_write_group(self):
662
"""Abort any pending changes."""
663
self.db.transaction_cancel()
666
return "%s(%r)" % (self.__class__.__name__, self.path)
668
def lookup_commit(self, revid):
670
return sha_to_hex(self.db[b"commit\0" + revid][:20])
672
raise KeyError("No cache entry for %r" % revid)
674
def lookup_blob_id(self, fileid, revision):
675
return sha_to_hex(self.db[b"\0".join((b"blob", fileid, revision))])
677
def lookup_git_sha(self, sha):
678
"""Lookup a Git sha in the database.
680
:param sha: Git object sha
681
:return: (type, type_data) with type_data:
682
commit: revid, tree sha
687
sha = hex_to_sha(sha)
688
value = self.db[b"git\0" + sha]
689
for data in value.splitlines():
690
data = data.split(b"\0")
691
type_name = data[0].decode('ascii')
692
if type_name == "commit":
694
yield (type_name, (data[1], data[2], {}))
696
yield (type_name, (data[1], data[2], {"testament3-sha1": data[3]}))
697
elif type_name in ("tree", "blob"):
698
yield (type_name, tuple(data[1:]))
700
raise AssertionError("unknown type %r" % type_name)
702
def missing_revisions(self, revids):
705
if self.db.get(b"commit\0" + revid) is None:
711
return self.db.keys()
712
except AttributeError: # python < 3
713
return self.db.iterkeys()
716
"""List the revision ids known."""
717
for key in self._keys():
718
if key.startswith(b"commit\0"):
722
"""List the SHA1s."""
723
for key in self._keys():
724
if key.startswith(b"git\0"):
725
yield sha_to_hex(key[4:])
728
class VersionedFilesContentCache(ContentCache):
730
def __init__(self, vf):
734
self._vf.insert_record_stream(
735
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
736
obj.as_legacy_object_chunks())])
738
def __getitem__(self, sha):
739
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
741
if entry.storage_kind == 'absent':
743
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
746
class IndexCacheUpdater(CacheUpdater):
748
def __init__(self, cache, rev):
750
self.revid = rev.revision_id
751
self.parent_revids = rev.parent_ids
755
def add_object(self, obj, bzr_key_data, path):
756
if isinstance(obj, tuple):
757
(type_name, hexsha) = obj
759
type_name = obj.type_name.decode('ascii')
761
if type_name == "commit":
763
if type(bzr_key_data) is not dict:
764
raise TypeError(bzr_key_data)
765
self.cache.idmap._add_git_sha(hexsha, b"commit",
766
(self.revid, obj.tree, bzr_key_data))
767
self.cache.idmap._add_node((b"commit", self.revid, b"X"),
768
b" ".join((hexsha, obj.tree)))
769
elif type_name == "blob":
770
self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
771
self.cache.idmap._add_node((b"blob", bzr_key_data[0],
772
bzr_key_data[1]), hexsha)
773
elif type_name == "tree":
774
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
782
class IndexBzrGitCache(BzrGitCache):
784
def __init__(self, transport=None):
785
mapper = versionedfile.ConstantMapper("trees")
786
shamap = IndexGitShaMap(transport.clone('index'))
787
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
790
class IndexGitCacheFormat(BzrGitCacheFormat):
792
def get_format_string(self):
793
return b'bzr-git sha map with git object cache version 1\n'
795
def initialize(self, transport):
796
super(IndexGitCacheFormat, self).initialize(transport)
797
transport.mkdir('index')
798
transport.mkdir('objects')
799
from .transportgit import TransportObjectStore
800
TransportObjectStore.init(transport.clone('objects'))
802
def open(self, transport):
803
return IndexBzrGitCache(transport)
806
class IndexGitShaMap(GitShaMap):
807
"""SHA Map that uses the Bazaar APIs to store a cache.
809
BTree Index file with the following contents:
811
("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
812
("commit", <revid>, "X") -> "<sha1> <tree-id>"
813
("blob", <fileid>, <revid>) -> <sha1>
817
def __init__(self, transport=None):
819
if transport is None:
820
self._transport = None
821
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
822
self._builder = self._index
825
self._transport = transport
826
self._index = _mod_index.CombinedGraphIndex([])
827
for name in self._transport.list_dir("."):
828
if not name.endswith(".rix"):
830
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
831
self._transport.stat(name).st_size)
832
self._index.insert_index(0, x)
835
def from_repository(cls, repository):
836
transport = getattr(repository, "_transport", None)
837
if transport is not None:
839
transport.mkdir('git')
840
except bzr_errors.FileExists:
842
return cls(transport.clone('git'))
843
from ..transport import get_transport
844
return cls(get_transport(get_cache_dir()))
847
if self._transport is not None:
848
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
850
return "%s()" % (self.__class__.__name__)
853
if self._builder is not None:
854
raise errors.BzrError('builder already open')
855
self.start_write_group()
856
self._builder.add_nodes(
857
((key, value) for (_, key, value) in
858
self._index.iter_all_entries()))
860
for name in self._transport.list_dir('.'):
861
if name.endswith('.rix'):
862
to_remove.append(name)
863
self.commit_write_group()
864
del self._index.indices[1:]
865
for name in to_remove:
866
self._transport.rename(name, name + '.old')
868
def start_write_group(self):
869
if self._builder is not None:
870
raise errors.BzrError('builder already open')
871
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
872
self._name = osutils.sha()
874
def commit_write_group(self):
875
if self._builder is None:
876
raise errors.BzrError('builder not open')
877
stream = self._builder.finish()
878
name = self._name.hexdigest() + ".rix"
879
size = self._transport.put_file(name, stream)
880
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
881
self._index.insert_index(0, index)
885
def abort_write_group(self):
886
if self._builder is None:
887
raise errors.BzrError('builder not open')
891
def _add_node(self, key, value):
895
self._builder.add_node(key, value)
900
def _get_entry(self, key):
901
entries = self._index.iter_entries([key])
903
return next(entries)[2]
904
except StopIteration:
905
if self._builder is None:
907
entries = self._builder.iter_entries([key])
909
return next(entries)[2]
910
except StopIteration:
913
def _iter_entries_prefix(self, prefix):
914
for entry in self._index.iter_entries_prefix([prefix]):
915
yield (entry[1], entry[2])
916
if self._builder is not None:
917
for entry in self._builder.iter_entries_prefix([prefix]):
918
yield (entry[1], entry[2])
920
def lookup_commit(self, revid):
921
return self._get_entry((b"commit", revid, b"X"))[:40]
923
def _add_git_sha(self, hexsha, type, type_data):
924
if hexsha is not None:
925
self._name.update(hexsha)
926
if type == b"commit":
927
td = (type_data[0], type_data[1])
929
td += (type_data[2]["testament3-sha1"],)
934
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
936
# This object is not represented in Git - perhaps an empty
938
self._name.update(type + b" ".join(type_data))
940
def lookup_blob_id(self, fileid, revision):
941
return self._get_entry((b"blob", fileid, revision))
943
def lookup_git_sha(self, sha):
945
sha = sha_to_hex(sha)
946
value = self._get_entry((b"git", sha, b"X"))
947
data = value.split(b" ", 3)
948
if data[0] == b"commit":
951
verifiers = {"testament3-sha1": data[3]}
956
yield ("commit", (data[1], data[2], verifiers))
958
yield (data[0].decode('ascii'), tuple(data[1:]))
961
"""List the revision ids known."""
962
for key, value in self._iter_entries_prefix((b"commit", None, None)):
965
def missing_revisions(self, revids):
966
"""Return set of all the revisions that are not present."""
967
missing_revids = set(revids)
968
for _, key, value in self._index.iter_entries((
969
(b"commit", revid, b"X") for revid in revids)):
970
missing_revids.remove(key[1])
971
return missing_revids
974
"""List the SHA1s."""
975
for key, value in self._iter_entries_prefix((b"git", None, None)):
979
formats = registry.Registry()
980
formats.register(TdbGitCacheFormat().get_format_string(),
982
formats.register(SqliteGitCacheFormat().get_format_string(),
983
SqliteGitCacheFormat())
984
formats.register(IndexGitCacheFormat().get_format_string(),
985
IndexGitCacheFormat())
986
# In the future, this will become the default:
987
formats.register('default', IndexGitCacheFormat())
990
def migrate_ancient_formats(repo_transport):
991
# Migrate older cache formats
992
repo_transport = remove_readonly_transport_decorator(repo_transport)
993
has_sqlite = repo_transport.has("git.db")
994
has_tdb = repo_transport.has("git.tdb")
995
if not has_sqlite or has_tdb:
998
repo_transport.mkdir("git")
999
except bzr_errors.FileExists:
1001
# Prefer migrating git.db over git.tdb, since the latter may not
1002
# be openable on some platforms.
1004
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
1005
repo_transport.rename("git.db", "git/idmap.db")
1007
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
1008
repo_transport.rename("git.tdb", "git/idmap.tdb")
1011
def remove_readonly_transport_decorator(transport):
1012
if transport.is_readonly():
1014
return transport._decorated
1015
except AttributeError:
1016
raise bzr_errors.ReadOnlyError(transport)
1020
def from_repository(repository):
1021
"""Open a cache file for a repository.
1023
If the repository is remote and there is no transport available from it
1024
this will use a local file in the users cache directory
1025
(typically ~/.cache/bazaar/git/)
1027
:param repository: A repository object
1029
repo_transport = getattr(repository, "_transport", None)
1030
if repo_transport is not None:
1032
migrate_ancient_formats(repo_transport)
1033
except bzr_errors.ReadOnlyError:
1034
pass # Not much we can do
1035
return BzrGitCacheFormat.from_repository(repository)