1
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
assert type(bzr_key_data) is dict
295
type_data = (self.revid, self._commit.tree, bzr_key_data)
296
self.cache.idmap._by_revid[self.revid] = obj.id
297
elif obj.type_name in ("blob", "tree"):
298
if bzr_key_data is not None:
299
if obj.type_name == "blob":
300
revision = bzr_key_data[1]
302
revision = self.revid
303
key = type_data = (bzr_key_data[0], revision)
304
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
307
entry = (obj.type_name, type_data)
308
self.cache.idmap._by_sha.setdefault(obj.id, {})[key] = entry
311
if self._commit is None:
312
raise AssertionError("No commit object added")
316
class DictGitShaMap(GitShaMap):
317
"""Git SHA map that uses a dictionary."""
324
def lookup_blob_id(self, fileid, revision):
325
return self._by_fileid[revision][fileid]
327
def lookup_git_sha(self, sha):
328
for entry in self._by_sha[sha].itervalues():
331
def lookup_tree_id(self, fileid, revision):
332
return self._by_fileid[revision][fileid]
334
def lookup_commit(self, revid):
335
return self._by_revid[revid]
338
for key, entries in self._by_sha.iteritems():
339
for (type, type_data) in entries.values():
344
return self._by_sha.iterkeys()
347
class SqliteCacheUpdater(CacheUpdater):
349
def __init__(self, cache, rev):
351
self.db = self.cache.idmap.db
352
self.revid = rev.revision_id
357
def add_object(self, obj, bzr_key_data, path):
358
if obj.type_name == "commit":
360
assert type(bzr_key_data) is dict
361
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
362
elif obj.type_name == "tree":
363
if bzr_key_data is not None:
364
self._trees.append((obj.id, bzr_key_data[0], self.revid))
365
elif obj.type_name == "blob":
366
if bzr_key_data is not None:
367
self._blobs.append((obj.id, bzr_key_data[0], bzr_key_data[1]))
372
if self._commit is None:
373
raise AssertionError("No commit object added")
375
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
378
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
381
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
382
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
386
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
389
class SqliteGitCacheFormat(BzrGitCacheFormat):
391
def get_format_string(self):
392
return 'bzr-git sha map version 1 using sqlite\n'
394
def open(self, transport):
396
basepath = transport.local_abspath(".")
397
except bzr_errors.NotLocalUrl:
398
basepath = get_cache_dir()
399
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
402
class SqliteGitShaMap(GitShaMap):
403
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
405
def __init__(self, path=None):
408
self.db = sqlite3.connect(":memory:")
410
if not mapdbs().has_key(path):
411
mapdbs()[path] = sqlite3.connect(path)
412
self.db = mapdbs()[path]
413
self.db.text_factory = str
414
self.db.executescript("""
415
create table if not exists commits(
416
sha1 text not null check(length(sha1) == 40),
418
tree_sha text not null check(length(tree_sha) == 40)
420
create index if not exists commit_sha1 on commits(sha1);
421
create unique index if not exists commit_revid on commits(revid);
422
create table if not exists blobs(
423
sha1 text not null check(length(sha1) == 40),
424
fileid text not null,
427
create index if not exists blobs_sha1 on blobs(sha1);
428
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
429
create table if not exists trees(
430
sha1 text unique not null check(length(sha1) == 40),
431
fileid text not null,
434
create unique index if not exists trees_sha1 on trees(sha1);
435
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
438
self.db.executescript(
439
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
440
except sqlite3.OperationalError:
441
pass # Column already exists.
444
return "%s(%r)" % (self.__class__.__name__, self.path)
446
def lookup_commit(self, revid):
447
cursor = self.db.execute("select sha1 from commits where revid = ?",
449
row = cursor.fetchone()
454
def commit_write_group(self):
457
def lookup_blob_id(self, fileid, revision):
458
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
461
raise KeyError(fileid)
463
def lookup_tree_id(self, fileid, revision):
464
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
467
raise KeyError(fileid)
469
def lookup_git_sha(self, sha):
470
"""Lookup a Git sha in the database.
472
:param sha: Git object sha
473
:return: (type, type_data) with type_data:
474
commit: revid, tree sha, verifiers
479
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
480
for row in cursor.fetchall():
482
if row[2] is not None:
483
verifiers = {"testament3-sha1": row[2]}
486
yield ("commit", (row[0], row[1], verifiers))
487
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
488
for row in cursor.fetchall():
491
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
492
for row in cursor.fetchall():
499
"""List the revision ids known."""
500
return (row for (row,) in self.db.execute("select revid from commits"))
503
"""List the SHA1s."""
504
for table in ("blobs", "commits", "trees"):
505
for (sha,) in self.db.execute("select sha1 from %s" % table):
509
class TdbCacheUpdater(CacheUpdater):
510
"""Cache updater for tdb-based caches."""
512
def __init__(self, cache, rev):
514
self.db = cache.idmap.db
515
self.revid = rev.revision_id
516
self.parent_revids = rev.parent_ids
520
def add_object(self, obj, bzr_key_data, path):
521
sha = obj.sha().digest()
522
if obj.type_name == "commit":
523
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
524
assert type(bzr_key_data) is dict, "was %r" % bzr_key_data
525
type_data = (self.revid, obj.tree)
527
type_data += (bzr_key_data["testament3-sha1"],)
531
elif obj.type_name == "blob":
532
if bzr_key_data is None:
534
self.db["\0".join(("blob", bzr_key_data[0], bzr_key_data[1]))] = sha
535
type_data = bzr_key_data
536
elif obj.type_name == "tree":
537
if bzr_key_data is None:
539
(file_id, ) = bzr_key_data
540
type_data = (file_id, self.revid)
543
entry = "\0".join((obj.type_name, ) + type_data) + "\n"
546
oldval = self.db[key]
550
if oldval[-1] != "\n":
551
self.db[key] = "".join([oldval, "\n", entry])
553
self.db[key] = "".join([oldval, entry])
556
if self._commit is None:
557
raise AssertionError("No commit object added")
561
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
564
class TdbGitCacheFormat(BzrGitCacheFormat):
565
"""Cache format for tdb-based caches."""
567
def get_format_string(self):
568
return 'bzr-git sha map version 3 using tdb\n'
570
def open(self, transport):
572
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
573
except bzr_errors.NotLocalUrl:
574
basepath = get_cache_dir()
575
assert isinstance(basepath, str)
577
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
580
"Unable to open existing bzr-git cache because 'tdb' is not "
584
class TdbGitShaMap(GitShaMap):
585
"""SHA Map that uses a TDB database.
589
"git <sha1>" -> "<type> <type-data1> <type-data2>"
590
"commit revid" -> "<sha1> <tree-id>"
591
"tree fileid revid" -> "<sha1>"
592
"blob fileid revid" -> "<sha1>"
596
TDB_HASH_SIZE = 50000
598
def __init__(self, path=None):
604
assert isinstance(path, str)
605
if not mapdbs().has_key(path):
606
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
607
os.O_RDWR|os.O_CREAT)
608
self.db = mapdbs()[path]
610
if int(self.db["version"]) not in (2, 3):
611
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
612
self.db["version"], self.TDB_MAP_VERSION)
616
self.db["version"] = str(self.TDB_MAP_VERSION)
618
def start_write_group(self):
619
"""Start writing changes."""
620
self.db.transaction_start()
622
def commit_write_group(self):
623
"""Commit any pending changes."""
624
self.db.transaction_commit()
626
def abort_write_group(self):
627
"""Abort any pending changes."""
628
self.db.transaction_cancel()
631
return "%s(%r)" % (self.__class__.__name__, self.path)
633
def lookup_commit(self, revid):
635
return sha_to_hex(self.db["commit\0" + revid][:20])
637
raise KeyError("No cache entry for %r" % revid)
639
def lookup_blob_id(self, fileid, revision):
640
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
642
def lookup_git_sha(self, sha):
643
"""Lookup a Git sha in the database.
645
:param sha: Git object sha
646
:return: (type, type_data) with type_data:
647
commit: revid, tree sha
652
sha = hex_to_sha(sha)
653
value = self.db["git\0" + sha]
654
for data in value.splitlines():
655
data = data.split("\0")
656
if data[0] == "commit":
658
yield (data[0], (data[1], data[2], {}))
660
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
661
elif data[0] in ("tree", "blob"):
662
yield (data[0], tuple(data[1:]))
664
raise AssertionError("unknown type %r" % data[0])
666
def missing_revisions(self, revids):
669
if self.db.get("commit\0" + revid) is None:
674
"""List the revision ids known."""
675
for key in self.db.iterkeys():
676
if key.startswith("commit\0"):
680
"""List the SHA1s."""
681
for key in self.db.iterkeys():
682
if key.startswith("git\0"):
683
yield sha_to_hex(key[4:])
686
class VersionedFilesContentCache(ContentCache):
688
def __init__(self, vf):
692
self._vf.insert_record_stream(
693
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
694
obj.as_legacy_object_chunks())])
696
def __getitem__(self, sha):
697
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
698
entry = stream.next()
699
if entry.storage_kind == 'absent':
701
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
704
class GitObjectStoreContentCache(ContentCache):
706
def __init__(self, store):
709
def add_multi(self, objs):
710
self.store.add_objects(objs)
712
def add(self, obj, path):
713
self.store.add_object(obj)
715
def __getitem__(self, sha):
716
return self.store[sha]
719
class IndexCacheUpdater(CacheUpdater):
721
def __init__(self, cache, rev):
723
self.revid = rev.revision_id
724
self.parent_revids = rev.parent_ids
727
self._cache_objs = set()
729
def add_object(self, obj, bzr_key_data, path):
730
if obj.type_name == "commit":
732
assert type(bzr_key_data) is dict
733
self.cache.idmap._add_git_sha(obj.id, "commit",
734
(self.revid, obj.tree, bzr_key_data))
735
self.cache.idmap._add_node(("commit", self.revid, "X"),
736
" ".join((obj.id, obj.tree)))
737
self._cache_objs.add((obj, path))
738
elif obj.type_name == "blob":
739
self.cache.idmap._add_git_sha(obj.id, "blob", bzr_key_data)
740
self.cache.idmap._add_node(("blob", bzr_key_data[0],
741
bzr_key_data[1]), obj.id)
742
elif obj.type_name == "tree":
743
self.cache.idmap._add_git_sha(obj.id, "tree",
744
(bzr_key_data[0], self.revid))
745
self._cache_objs.add((obj, path))
750
self.cache.content_cache.add_multi(self._cache_objs)
754
class IndexBzrGitCache(BzrGitCache):
756
def __init__(self, transport=None):
757
mapper = versionedfile.ConstantMapper("trees")
758
shamap = IndexGitShaMap(transport.clone('index'))
759
#trees_store = knit.make_file_factory(True, mapper)(transport)
760
#content_cache = VersionedFilesContentCache(trees_store)
761
from .transportgit import TransportObjectStore
762
store = TransportObjectStore(transport.clone('objects'))
763
content_cache = GitObjectStoreContentCache(store)
764
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
768
class IndexGitCacheFormat(BzrGitCacheFormat):
770
def get_format_string(self):
771
return 'bzr-git sha map with git object cache version 1\n'
773
def initialize(self, transport):
774
super(IndexGitCacheFormat, self).initialize(transport)
775
transport.mkdir('index')
776
transport.mkdir('objects')
777
from .transportgit import TransportObjectStore
778
TransportObjectStore.init(transport.clone('objects'))
780
def open(self, transport):
781
return IndexBzrGitCache(transport)
784
class IndexGitShaMap(GitShaMap):
785
"""SHA Map that uses the Bazaar APIs to store a cache.
787
BTree Index file with the following contents:
789
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
790
("commit", <revid>) -> "<sha1> <tree-id>"
791
("blob", <fileid>, <revid>) -> <sha1>
795
def __init__(self, transport=None):
796
if transport is None:
797
self._transport = None
798
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
799
self._builder = self._index
802
self._transport = transport
803
self._index = _mod_index.CombinedGraphIndex([])
804
for name in self._transport.list_dir("."):
805
if not name.endswith(".rix"):
807
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
808
self._transport.stat(name).st_size)
809
self._index.insert_index(0, x)
812
def from_repository(cls, repository):
813
transport = getattr(repository, "_transport", None)
814
if transport is not None:
816
transport.mkdir('git')
817
except bzr_errors.FileExists:
819
return cls(transport.clone('git'))
820
from ...transport import get_transport
821
return cls(get_transport(get_cache_dir()))
824
if self._transport is not None:
825
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
827
return "%s()" % (self.__class__.__name__)
830
assert self._builder is None
831
self.start_write_group()
832
for _, key, value in self._index.iter_all_entries():
833
self._builder.add_node(key, value)
835
for name in self._transport.list_dir('.'):
836
if name.endswith('.rix'):
837
to_remove.append(name)
838
self.commit_write_group()
839
del self._index.indices[1:]
840
for name in to_remove:
841
self._transport.rename(name, name + '.old')
843
def start_write_group(self):
844
assert self._builder is None
845
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
846
self._name = osutils.sha()
848
def commit_write_group(self):
849
assert self._builder is not None
850
stream = self._builder.finish()
851
name = self._name.hexdigest() + ".rix"
852
size = self._transport.put_file(name, stream)
853
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
854
self._index.insert_index(0, index)
858
def abort_write_group(self):
859
assert self._builder is not None
863
def _add_node(self, key, value):
865
self._builder.add_node(key, value)
866
except bzr_errors.BadIndexDuplicateKey:
867
# Multiple bzr objects can have the same contents
872
def _get_entry(self, key):
873
entries = self._index.iter_entries([key])
875
return entries.next()[2]
876
except StopIteration:
877
if self._builder is None:
879
entries = self._builder.iter_entries([key])
881
return entries.next()[2]
882
except StopIteration:
885
def _iter_entries_prefix(self, prefix):
886
for entry in self._index.iter_entries_prefix([prefix]):
887
yield (entry[1], entry[2])
888
if self._builder is not None:
889
for entry in self._builder.iter_entries_prefix([prefix]):
890
yield (entry[1], entry[2])
892
def lookup_commit(self, revid):
893
return self._get_entry(("commit", revid, "X"))[:40]
895
def _add_git_sha(self, hexsha, type, type_data):
896
if hexsha is not None:
897
self._name.update(hexsha)
899
td = (type_data[0], type_data[1])
901
td += (type_data[2]["testament3-sha1"],)
906
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
908
# This object is not represented in Git - perhaps an empty
910
self._name.update(type + " ".join(type_data))
912
def lookup_blob_id(self, fileid, revision):
913
return self._get_entry(("blob", fileid, revision))
915
def lookup_git_sha(self, sha):
917
sha = sha_to_hex(sha)
919
for key, value in self._iter_entries_prefix(("git", sha, None)):
921
data = value.split(" ", 3)
922
if data[0] == "commit":
924
verifiers = {"testament3-sha1": data[3]}
927
yield ("commit", (data[1], data[2], verifiers))
929
yield (data[0], tuple(data[1:]))
934
"""List the revision ids known."""
935
for key, value in self._iter_entries_prefix(("commit", None, None)):
938
def missing_revisions(self, revids):
939
"""Return set of all the revisions that are not present."""
940
missing_revids = set(revids)
941
for _, key, value in self._index.iter_entries((
942
("commit", revid, "X") for revid in revids)):
943
missing_revids.remove(key[1])
944
return missing_revids
947
"""List the SHA1s."""
948
for key, value in self._iter_entries_prefix(("git", None, None)):
952
formats = registry.Registry()
953
formats.register(TdbGitCacheFormat().get_format_string(),
955
formats.register(SqliteGitCacheFormat().get_format_string(),
956
SqliteGitCacheFormat())
957
formats.register(IndexGitCacheFormat().get_format_string(),
958
IndexGitCacheFormat())
959
# In the future, this will become the default:
960
# formats.register('default', IndexGitCacheFormat())
964
formats.register('default', SqliteGitCacheFormat())
966
formats.register('default', TdbGitCacheFormat())
970
def migrate_ancient_formats(repo_transport):
971
# Migrate older cache formats
972
repo_transport = remove_readonly_transport_decorator(repo_transport)
973
has_sqlite = repo_transport.has("git.db")
974
has_tdb = repo_transport.has("git.tdb")
975
if not has_sqlite or has_tdb:
978
repo_transport.mkdir("git")
979
except bzr_errors.FileExists:
981
# Prefer migrating git.db over git.tdb, since the latter may not
982
# be openable on some platforms.
984
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
985
repo_transport.rename("git.db", "git/idmap.db")
987
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
988
repo_transport.rename("git.tdb", "git/idmap.tdb")
991
def remove_readonly_transport_decorator(transport):
992
if transport.is_readonly():
994
return transport._decorated
995
except AttributeError:
996
raise bzr_errors.ReadOnlyError(transport)
1000
def from_repository(repository):
1001
"""Open a cache file for a repository.
1003
If the repository is remote and there is no transport available from it
1004
this will use a local file in the users cache directory
1005
(typically ~/.cache/bazaar/git/)
1007
:param repository: A repository object
1009
repo_transport = getattr(repository, "_transport", None)
1010
if repo_transport is not None:
1012
migrate_ancient_formats(repo_transport)
1013
except bzr_errors.ReadOnlyError:
1014
pass # Not much we can do
1015
return BzrGitCacheFormat.from_repository(repository)