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 dulwich.objects import (
26
from dulwich.objects import (
32
btree_index as _mod_btree_index,
39
from bzrlib.transport import (
46
from xdg.BaseDirectory import xdg_cache_home
48
from bzrlib.config import config_dir
49
ret = os.path.join(config_dir(), "git")
51
ret = os.path.join(xdg_cache_home, "bazaar", "git")
52
if not os.path.isdir(ret):
57
def get_remote_cache_transport(repository):
58
"""Retrieve the transport to use when accessing (unwritable) remote
61
uuid = getattr(repository, "uuid", None)
63
path = get_cache_dir()
65
path = os.path.join(get_cache_dir(), uuid)
66
if not os.path.isdir(path):
68
return get_transport(path)
71
def check_pysqlite_version(sqlite3):
72
"""Check that sqlite library is compatible.
75
if (sqlite3.sqlite_version_info[0] < 3 or
76
(sqlite3.sqlite_version_info[0] == 3 and
77
sqlite3.sqlite_version_info[1] < 3)):
78
trace.warning('Needs at least sqlite 3.3.x')
79
raise bzrlib.errors.BzrError("incompatible sqlite library")
84
check_pysqlite_version(sqlite3)
85
except (ImportError, bzrlib.errors.BzrError), e:
86
from pysqlite2 import dbapi2 as sqlite3
87
check_pysqlite_version(sqlite3)
89
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
91
raise bzrlib.errors.BzrError("missing sqlite library")
94
_mapdbs = threading.local()
96
"""Get a cache for this thread's db connections."""
99
except AttributeError:
104
class GitShaMap(object):
105
"""Git<->Bzr revision id mapping database."""
107
def lookup_git_sha(self, sha):
108
"""Lookup a Git sha in the database.
109
:param sha: Git object sha
110
:return: list with (type, type_data) tuples with type_data:
111
commit: revid, tree_sha, verifiers
115
raise NotImplementedError(self.lookup_git_sha)
117
def lookup_blob_id(self, file_id, revision):
118
"""Retrieve a Git blob SHA by file id.
120
:param file_id: File id of the file/symlink
121
:param revision: revision in which the file was last changed.
123
raise NotImplementedError(self.lookup_blob_id)
125
def lookup_tree_id(self, file_id, revision):
126
"""Retrieve a Git tree SHA by file id.
128
raise NotImplementedError(self.lookup_tree_id)
130
def lookup_commit(self, revid):
131
"""Retrieve a Git commit SHA by Bazaar revision id.
133
raise NotImplementedError(self.lookup_commit)
136
"""List the revision ids known."""
137
raise NotImplementedError(self.revids)
139
def missing_revisions(self, revids):
140
"""Return set of all the revisions that are not present."""
141
present_revids = set(self.revids())
142
if not isinstance(revids, set):
144
return revids - present_revids
147
"""List the SHA1s."""
148
raise NotImplementedError(self.sha1s)
150
def start_write_group(self):
151
"""Start writing changes."""
153
def commit_write_group(self):
154
"""Commit any pending changes."""
156
def abort_write_group(self):
157
"""Abort any pending changes."""
160
class ContentCache(object):
161
"""Object that can cache Git objects."""
163
def add(self, object):
165
raise NotImplementedError(self.add)
167
def add_multi(self, objects):
168
"""Add multiple objects."""
172
def __getitem__(self, sha):
173
"""Retrieve an item, by SHA."""
174
raise NotImplementedError(self.__getitem__)
177
class BzrGitCacheFormat(object):
178
"""Bazaar-Git Cache Format."""
180
def get_format_string(self):
181
"""Return a single-line unique format string for this cache format."""
182
raise NotImplementedError(self.get_format_string)
184
def open(self, transport):
185
"""Open this format on a transport."""
186
raise NotImplementedError(self.open)
188
def initialize(self, transport):
189
"""Create a new instance of this cache format at transport."""
190
transport.put_bytes('format', self.get_format_string())
193
def from_transport(self, transport):
194
"""Open a cache file present on a transport, or initialize one.
196
:param transport: Transport to use
197
:return: A BzrGitCache instance
200
format_name = transport.get_bytes('format')
201
format = formats.get(format_name)
202
except bzrlib.errors.NoSuchFile:
203
format = formats.get('default')
204
format.initialize(transport)
205
return format.open(transport)
208
def from_repository(cls, repository):
209
"""Open a cache file for a repository.
211
This will use the repository's transport to store the cache file, or
212
use the users global cache directory if the repository has no
213
transport associated with it.
215
:param repository: Repository to open the cache for
216
:return: A `BzrGitCache`
218
from bzrlib.transport.local import LocalTransport
219
repo_transport = getattr(repository, "_transport", None)
220
if repo_transport is not None and isinstance(repo_transport, LocalTransport):
221
# Even if we don't write to this repo, we should be able
222
# to update its cache.
223
repo_transport = remove_readonly_transport_decorator(repo_transport)
225
repo_transport.mkdir('git')
226
except bzrlib.errors.FileExists:
228
transport = repo_transport.clone('git')
230
transport = get_remote_cache_transport(repository)
231
return cls.from_transport(transport)
234
class CacheUpdater(object):
235
"""Base class for objects that can update a bzr-git cache."""
237
def add_object(self, obj, ie, path):
240
:param obj: Object type ("commit", "blob" or "tree")
241
:param ie: Inventory entry (for blob/tree) or testament_sha in case
243
:param path: Path of the object (optional)
245
raise NotImplementedError(self.add_object)
248
raise NotImplementedError(self.finish)
251
class BzrGitCache(object):
252
"""Caching backend."""
254
def __init__(self, idmap, content_cache, cache_updater_klass):
256
self.content_cache = content_cache
257
self._cache_updater_klass = cache_updater_klass
259
def get_updater(self, rev):
260
"""Update an object that implements the CacheUpdater interface for
263
return self._cache_updater_klass(self, rev)
266
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
269
class DictCacheUpdater(CacheUpdater):
270
"""Cache updater for dict-based caches."""
272
def __init__(self, cache, rev):
274
self.revid = rev.revision_id
275
self.parent_revids = rev.parent_ids
279
def add_object(self, obj, ie, path):
280
if obj.type_name == "commit":
282
assert type(ie) is dict
284
type_data = (self.revid, self._commit.tree, ie)
285
self.cache.idmap._by_revid[self.revid] = obj.id
286
elif obj.type_name in ("blob", "tree"):
288
if obj.type_name == "blob":
289
revision = ie.revision
291
revision = self.revid
292
key = type_data = (ie.file_id, revision)
293
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
296
entry = (obj.type_name, type_data)
297
self.cache.idmap._by_sha.setdefault(obj.id, {})[key] = entry
300
if self._commit is None:
301
raise AssertionError("No commit object added")
305
class DictGitShaMap(GitShaMap):
306
"""Git SHA map that uses a dictionary."""
313
def lookup_blob_id(self, fileid, revision):
314
return self._by_fileid[revision][fileid]
316
def lookup_git_sha(self, sha):
317
for entry in self._by_sha[sha].itervalues():
320
def lookup_tree_id(self, fileid, revision):
321
return self._by_fileid[revision][fileid]
323
def lookup_commit(self, revid):
324
return self._by_revid[revid]
327
for key, entries in self._by_sha.iteritems():
328
for (type, type_data) in entries.values():
333
return self._by_sha.iterkeys()
336
class SqliteCacheUpdater(CacheUpdater):
338
def __init__(self, cache, rev):
340
self.db = self.cache.idmap.db
341
self.revid = rev.revision_id
346
def add_object(self, obj, ie, path):
347
if obj.type_name == "commit":
349
self._testament3_sha1 = ie.get("testament3-sha1")
350
assert type(ie) is dict
351
elif obj.type_name == "tree":
353
self._trees.append((obj.id, ie.file_id, self.revid))
354
elif obj.type_name == "blob":
356
self._blobs.append((obj.id, ie.file_id, ie.revision))
361
if self._commit is None:
362
raise AssertionError("No commit object added")
364
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
367
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
370
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
371
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
375
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
378
class SqliteGitCacheFormat(BzrGitCacheFormat):
380
def get_format_string(self):
381
return 'bzr-git sha map version 1 using sqlite\n'
383
def open(self, transport):
385
basepath = transport.local_abspath(".")
386
except bzrlib.errors.NotLocalUrl:
387
basepath = get_cache_dir()
388
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
391
class SqliteGitShaMap(GitShaMap):
392
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
394
def __init__(self, path=None):
397
self.db = sqlite3.connect(":memory:")
399
if not mapdbs().has_key(path):
400
mapdbs()[path] = sqlite3.connect(path)
401
self.db = mapdbs()[path]
402
self.db.text_factory = str
403
self.db.executescript("""
404
create table if not exists commits(
405
sha1 text not null check(length(sha1) == 40),
407
tree_sha text not null check(length(tree_sha) == 40)
409
create index if not exists commit_sha1 on commits(sha1);
410
create unique index if not exists commit_revid on commits(revid);
411
create table if not exists blobs(
412
sha1 text not null check(length(sha1) == 40),
413
fileid text not null,
416
create index if not exists blobs_sha1 on blobs(sha1);
417
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
418
create table if not exists trees(
419
sha1 text unique not null check(length(sha1) == 40),
420
fileid text not null,
423
create unique index if not exists trees_sha1 on trees(sha1);
424
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
427
self.db.executescript(
428
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
429
except sqlite3.OperationalError:
430
pass # Column already exists.
433
return "%s(%r)" % (self.__class__.__name__, self.path)
435
def lookup_commit(self, revid):
436
cursor = self.db.execute("select sha1 from commits where revid = ?",
438
row = cursor.fetchone()
443
def commit_write_group(self):
446
def lookup_blob_id(self, fileid, revision):
447
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
450
raise KeyError(fileid)
452
def lookup_tree_id(self, fileid, revision):
453
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
456
raise KeyError(fileid)
458
def lookup_git_sha(self, sha):
459
"""Lookup a Git sha in the database.
461
:param sha: Git object sha
462
:return: (type, type_data) with type_data:
463
commit: revid, tree sha, verifiers
468
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
469
for row in cursor.fetchall():
471
if row[2] is not None:
472
verifiers = {"testament3-sha1": row[2]}
475
yield ("commit", (row[0], row[1], verifiers))
476
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
477
for row in cursor.fetchall():
480
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
481
for row in cursor.fetchall():
488
"""List the revision ids known."""
489
return (row for (row,) in self.db.execute("select revid from commits"))
492
"""List the SHA1s."""
493
for table in ("blobs", "commits", "trees"):
494
for (sha,) in self.db.execute("select sha1 from %s" % table):
498
class TdbCacheUpdater(CacheUpdater):
499
"""Cache updater for tdb-based caches."""
501
def __init__(self, cache, rev):
503
self.db = cache.idmap.db
504
self.revid = rev.revision_id
505
self.parent_revids = rev.parent_ids
509
def add_object(self, obj, ie, path):
510
sha = obj.sha().digest()
511
if obj.type_name == "commit":
512
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
513
assert type(ie) is dict, "was %r" % ie
514
type_data = (self.revid, obj.tree)
516
type_data += (ie["testament3-sha1"],)
520
elif obj.type_name == "blob":
523
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
524
type_data = (ie.file_id, ie.revision)
525
elif obj.type_name == "tree":
528
type_data = (ie.file_id, self.revid)
531
entry = "\0".join((obj.type_name, ) + type_data) + "\n"
534
oldval = self.db[key]
538
if oldval[-1] != "\n":
539
self.db[key] = "".join([oldval, "\n", entry])
541
self.db[key] = "".join([oldval, entry])
544
if self._commit is None:
545
raise AssertionError("No commit object added")
549
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
552
class TdbGitCacheFormat(BzrGitCacheFormat):
553
"""Cache format for tdb-based caches."""
555
def get_format_string(self):
556
return 'bzr-git sha map version 3 using tdb\n'
558
def open(self, transport):
560
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
561
except bzrlib.errors.NotLocalUrl:
562
basepath = get_cache_dir()
563
assert isinstance(basepath, str)
565
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
568
"Unable to open existing bzr-git cache because 'tdb' is not "
572
class TdbGitShaMap(GitShaMap):
573
"""SHA Map that uses a TDB database.
577
"git <sha1>" -> "<type> <type-data1> <type-data2>"
578
"commit revid" -> "<sha1> <tree-id>"
579
"tree fileid revid" -> "<sha1>"
580
"blob fileid revid" -> "<sha1>"
584
TDB_HASH_SIZE = 50000
586
def __init__(self, path=None):
592
assert isinstance(path, str)
593
if not mapdbs().has_key(path):
594
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
595
os.O_RDWR|os.O_CREAT)
596
self.db = mapdbs()[path]
598
if int(self.db["version"]) not in (2, 3):
599
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
600
self.db["version"], self.TDB_MAP_VERSION)
604
self.db["version"] = str(self.TDB_MAP_VERSION)
606
def start_write_group(self):
607
"""Start writing changes."""
608
self.db.transaction_start()
610
def commit_write_group(self):
611
"""Commit any pending changes."""
612
self.db.transaction_commit()
614
def abort_write_group(self):
615
"""Abort any pending changes."""
616
self.db.transaction_cancel()
619
return "%s(%r)" % (self.__class__.__name__, self.path)
621
def lookup_commit(self, revid):
622
return sha_to_hex(self.db["commit\0" + revid][:20])
624
def lookup_blob_id(self, fileid, revision):
625
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
627
def lookup_git_sha(self, sha):
628
"""Lookup a Git sha in the database.
630
:param sha: Git object sha
631
:return: (type, type_data) with type_data:
632
commit: revid, tree sha
637
sha = hex_to_sha(sha)
638
value = self.db["git\0" + sha]
639
for data in value.splitlines():
640
data = data.split("\0")
641
if data[0] == "commit":
643
yield (data[0], (data[1], data[2], {}))
645
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
646
elif data[0] in ("tree", "blob"):
647
yield (data[0], tuple(data[1:]))
649
raise AssertionError("unknown type %r" % data[0])
651
def missing_revisions(self, revids):
654
if self.db.get("commit\0" + revid) is None:
659
"""List the revision ids known."""
660
for key in self.db.iterkeys():
661
if key.startswith("commit\0"):
665
"""List the SHA1s."""
666
for key in self.db.iterkeys():
667
if key.startswith("git\0"):
668
yield sha_to_hex(key[4:])
671
class VersionedFilesContentCache(ContentCache):
673
def __init__(self, vf):
677
self._vf.insert_record_stream(
678
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
679
obj.as_legacy_object_chunks())])
681
def __getitem__(self, sha):
682
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
683
entry = stream.next()
684
if entry.storage_kind == 'absent':
686
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
689
class GitObjectStoreContentCache(ContentCache):
691
def __init__(self, store):
694
def add_multi(self, objs):
695
self.store.add_objects(objs)
697
def add(self, obj, path):
698
self.store.add_object(obj)
700
def __getitem__(self, sha):
701
return self.store[sha]
704
class IndexCacheUpdater(CacheUpdater):
706
def __init__(self, cache, rev):
708
self.revid = rev.revision_id
709
self.parent_revids = rev.parent_ids
712
self._cache_objs = set()
714
def add_object(self, obj, ie, path):
715
if obj.type_name == "commit":
717
assert type(ie) is dict
718
self.cache.idmap._add_git_sha(obj.id, "commit",
719
(self.revid, obj.tree, ie))
720
self.cache.idmap._add_node(("commit", self.revid, "X"),
721
" ".join((obj.id, obj.tree)))
722
self._cache_objs.add((obj, path))
723
elif obj.type_name == "blob":
724
self.cache.idmap._add_git_sha(obj.id, "blob",
725
(ie.file_id, ie.revision))
726
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
727
if ie.kind == "symlink":
728
self._cache_objs.add((obj, path))
729
elif obj.type_name == "tree":
730
self.cache.idmap._add_git_sha(obj.id, "tree",
731
(ie.file_id, self.revid))
732
self._cache_objs.add((obj, path))
737
self.cache.content_cache.add_multi(self._cache_objs)
741
class IndexBzrGitCache(BzrGitCache):
743
def __init__(self, transport=None):
744
mapper = versionedfile.ConstantMapper("trees")
745
shamap = IndexGitShaMap(transport.clone('index'))
746
#trees_store = knit.make_file_factory(True, mapper)(transport)
747
#content_cache = VersionedFilesContentCache(trees_store)
748
from bzrlib.plugins.git.transportgit import TransportObjectStore
749
store = TransportObjectStore(transport.clone('objects'))
750
content_cache = GitObjectStoreContentCache(store)
751
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
755
class IndexGitCacheFormat(BzrGitCacheFormat):
757
def get_format_string(self):
758
return 'bzr-git sha map with git object cache version 1\n'
760
def initialize(self, transport):
761
super(IndexGitCacheFormat, self).initialize(transport)
762
transport.mkdir('index')
763
transport.mkdir('objects')
764
from bzrlib.plugins.git.transportgit import TransportObjectStore
765
TransportObjectStore.init(transport.clone('objects'))
767
def open(self, transport):
768
return IndexBzrGitCache(transport)
771
class IndexGitShaMap(GitShaMap):
772
"""SHA Map that uses the Bazaar APIs to store a cache.
774
BTree Index file with the following contents:
776
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
777
("commit", <revid>) -> "<sha1> <tree-id>"
778
("blob", <fileid>, <revid>) -> <sha1>
782
def __init__(self, transport=None):
783
if transport is None:
784
self._transport = None
785
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
786
self._builder = self._index
789
self._transport = transport
790
self._index = _mod_index.CombinedGraphIndex([])
791
for name in self._transport.list_dir("."):
792
if not name.endswith(".rix"):
794
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
795
self._transport.stat(name).st_size)
796
self._index.insert_index(0, x)
799
def from_repository(cls, repository):
800
transport = getattr(repository, "_transport", None)
801
if transport is not None:
803
transport.mkdir('git')
804
except bzrlib.errors.FileExists:
806
return cls(transport.clone('git'))
807
from bzrlib.transport import get_transport
808
return cls(get_transport(get_cache_dir()))
811
if self._transport is not None:
812
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
814
return "%s()" % (self.__class__.__name__)
817
assert self._builder is None
818
self.start_write_group()
819
for _, key, value in self._index.iter_all_entries():
820
self._builder.add_node(key, value)
822
for name in self._transport.list_dir('.'):
823
if name.endswith('.rix'):
824
to_remove.append(name)
825
self.commit_write_group()
826
del self._index.indices[1:]
827
for name in to_remove:
828
self._transport.rename(name, name + '.old')
830
def start_write_group(self):
831
assert self._builder is None
832
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
833
self._name = osutils.sha()
835
def commit_write_group(self):
836
assert self._builder is not None
837
stream = self._builder.finish()
838
name = self._name.hexdigest() + ".rix"
839
size = self._transport.put_file(name, stream)
840
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
841
self._index.insert_index(0, index)
845
def abort_write_group(self):
846
assert self._builder is not None
850
def _add_node(self, key, value):
852
self._builder.add_node(key, value)
853
except bzrlib.errors.BadIndexDuplicateKey:
854
# Multiple bzr objects can have the same contents
859
def _get_entry(self, key):
860
entries = self._index.iter_entries([key])
862
return entries.next()[2]
863
except StopIteration:
864
if self._builder is None:
866
entries = self._builder.iter_entries([key])
868
return entries.next()[2]
869
except StopIteration:
872
def _iter_entries_prefix(self, prefix):
873
for entry in self._index.iter_entries_prefix([prefix]):
874
yield (entry[1], entry[2])
875
if self._builder is not None:
876
for entry in self._builder.iter_entries_prefix([prefix]):
877
yield (entry[1], entry[2])
879
def lookup_commit(self, revid):
880
return self._get_entry(("commit", revid, "X"))[:40]
882
def _add_git_sha(self, hexsha, type, type_data):
883
if hexsha is not None:
884
self._name.update(hexsha)
886
td = (type_data[0], type_data[1])
888
td += (type_data[2]["testament3-sha1"],)
893
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
895
# This object is not represented in Git - perhaps an empty
897
self._name.update(type + " ".join(type_data))
899
def lookup_blob_id(self, fileid, revision):
900
return self._get_entry(("blob", fileid, revision))
902
def lookup_git_sha(self, sha):
904
sha = sha_to_hex(sha)
906
for key, value in self._iter_entries_prefix(("git", sha, None)):
908
data = value.split(" ", 3)
909
if data[0] == "commit":
911
verifiers = {"testament3-sha1": data[3]}
914
yield ("commit", (data[1], data[2], verifiers))
916
yield (data[0], tuple(data[1:]))
921
"""List the revision ids known."""
922
for key, value in self._iter_entries_prefix(("commit", None, None)):
925
def missing_revisions(self, revids):
926
"""Return set of all the revisions that are not present."""
927
missing_revids = set(revids)
928
for _, key, value in self._index.iter_entries((
929
("commit", revid, "X") for revid in revids)):
930
missing_revids.remove(key[1])
931
return missing_revids
934
"""List the SHA1s."""
935
for key, value in self._iter_entries_prefix(("git", None, None)):
939
formats = registry.Registry()
940
formats.register(TdbGitCacheFormat().get_format_string(),
942
formats.register(SqliteGitCacheFormat().get_format_string(),
943
SqliteGitCacheFormat())
944
formats.register(IndexGitCacheFormat().get_format_string(),
945
IndexGitCacheFormat())
946
# In the future, this will become the default:
947
# formats.register('default', IndexGitCacheFormat())
951
formats.register('default', SqliteGitCacheFormat())
953
formats.register('default', TdbGitCacheFormat())
957
def migrate_ancient_formats(repo_transport):
958
# Migrate older cache formats
959
repo_transport = remove_readonly_transport_decorator(repo_transport)
960
has_sqlite = repo_transport.has("git.db")
961
has_tdb = repo_transport.has("git.tdb")
962
if not has_sqlite or has_tdb:
965
repo_transport.mkdir("git")
966
except bzrlib.errors.FileExists:
968
# Prefer migrating git.db over git.tdb, since the latter may not
969
# be openable on some platforms.
971
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
972
repo_transport.rename("git.db", "git/idmap.db")
974
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
975
repo_transport.rename("git.tdb", "git/idmap.tdb")
978
def remove_readonly_transport_decorator(transport):
979
if transport.is_readonly():
980
return transport._decorated
984
def from_repository(repository):
985
"""Open a cache file for a repository.
987
If the repository is remote and there is no transport available from it
988
this will use a local file in the users cache directory
989
(typically ~/.cache/bazaar/git/)
991
:param repository: A repository object
993
repo_transport = getattr(repository, "_transport", None)
994
if repo_transport is not None:
995
migrate_ancient_formats(repo_transport)
996
return BzrGitCacheFormat.from_repository(repository)