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():
58
"""Retrieve the transport to use when accessing (unwritable) remote
61
return get_transport(get_cache_dir())
64
def check_pysqlite_version(sqlite3):
65
"""Check that sqlite library is compatible.
68
if (sqlite3.sqlite_version_info[0] < 3 or
69
(sqlite3.sqlite_version_info[0] == 3 and
70
sqlite3.sqlite_version_info[1] < 3)):
71
trace.warning('Needs at least sqlite 3.3.x')
72
raise bzrlib.errors.BzrError("incompatible sqlite library")
77
check_pysqlite_version(sqlite3)
78
except (ImportError, bzrlib.errors.BzrError), e:
79
from pysqlite2 import dbapi2 as sqlite3
80
check_pysqlite_version(sqlite3)
82
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
84
raise bzrlib.errors.BzrError("missing sqlite library")
87
_mapdbs = threading.local()
89
"""Get a cache for this thread's db connections."""
92
except AttributeError:
97
class GitShaMap(object):
98
"""Git<->Bzr revision id mapping database."""
100
def lookup_git_sha(self, sha):
101
"""Lookup a Git sha in the database.
102
:param sha: Git object sha
103
:return: list with (type, type_data) tuples with type_data:
104
commit: revid, tree_sha, verifiers
108
raise NotImplementedError(self.lookup_git_sha)
110
def lookup_blob_id(self, file_id, revision):
111
"""Retrieve a Git blob SHA by file id.
113
:param file_id: File id of the file/symlink
114
:param revision: revision in which the file was last changed.
116
raise NotImplementedError(self.lookup_blob_id)
118
def lookup_tree_id(self, file_id, revision):
119
"""Retrieve a Git tree SHA by file id.
121
raise NotImplementedError(self.lookup_tree_id)
123
def lookup_commit(self, revid):
124
"""Retrieve a Git commit SHA by Bazaar revision id.
126
raise NotImplementedError(self.lookup_commit)
129
"""List the revision ids known."""
130
raise NotImplementedError(self.revids)
132
def missing_revisions(self, revids):
133
"""Return set of all the revisions that are not present."""
134
present_revids = set(self.revids())
135
if not isinstance(revids, set):
137
return revids - present_revids
140
"""List the SHA1s."""
141
raise NotImplementedError(self.sha1s)
143
def start_write_group(self):
144
"""Start writing changes."""
146
def commit_write_group(self):
147
"""Commit any pending changes."""
149
def abort_write_group(self):
150
"""Abort any pending changes."""
153
class ContentCache(object):
154
"""Object that can cache Git objects."""
156
def add(self, object):
158
raise NotImplementedError(self.add)
160
def add_multi(self, objects):
161
"""Add multiple objects."""
165
def __getitem__(self, sha):
166
"""Retrieve an item, by SHA."""
167
raise NotImplementedError(self.__getitem__)
170
class BzrGitCacheFormat(object):
171
"""Bazaar-Git Cache Format."""
173
def get_format_string(self):
174
"""Return a single-line unique format string for this cache format."""
175
raise NotImplementedError(self.get_format_string)
177
def open(self, transport):
178
"""Open this format on a transport."""
179
raise NotImplementedError(self.open)
181
def initialize(self, transport):
182
"""Create a new instance of this cache format at transport."""
183
transport.put_bytes('format', self.get_format_string())
186
def from_transport(self, transport):
187
"""Open a cache file present on a transport, or initialize one.
189
:param transport: Transport to use
190
:return: A BzrGitCache instance
193
format_name = transport.get_bytes('format')
194
format = formats.get(format_name)
195
except bzrlib.errors.NoSuchFile:
196
format = formats.get('default')
197
format.initialize(transport)
198
return format.open(transport)
201
def from_repository(cls, repository):
202
"""Open a cache file for a repository.
204
This will use the repository's transport to store the cache file, or
205
use the users global cache directory if the repository has no
206
transport associated with it.
208
:param repository: Repository to open the cache for
209
:return: A `BzrGitCache`
211
repo_transport = getattr(repository, "_transport", None)
212
if repo_transport is not None:
213
# Even if we don't write to this repo, we should be able
214
# to update its cache.
215
repo_transport = remove_readonly_transport_decorator(repo_transport)
217
repo_transport.mkdir('git')
218
except bzrlib.errors.FileExists:
220
transport = repo_transport.clone('git')
222
transport = get_remote_cache_transport()
223
return cls.from_transport(transport)
226
class CacheUpdater(object):
227
"""Base class for objects that can update a bzr-git cache."""
229
def add_object(self, obj, ie, path):
232
:param obj: Object type ("commit", "blob" or "tree")
233
:param ie: Inventory entry (for blob/tree) or testament_sha in case
235
:param path: Path of the object (optional)
237
raise NotImplementedError(self.add_object)
240
raise NotImplementedError(self.finish)
243
class BzrGitCache(object):
244
"""Caching backend."""
246
def __init__(self, idmap, content_cache, cache_updater_klass):
248
self.content_cache = content_cache
249
self._cache_updater_klass = cache_updater_klass
251
def get_updater(self, rev):
252
"""Update an object that implements the CacheUpdater interface for
255
return self._cache_updater_klass(self, rev)
258
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
261
class DictCacheUpdater(CacheUpdater):
262
"""Cache updater for dict-based caches."""
264
def __init__(self, cache, rev):
266
self.revid = rev.revision_id
267
self.parent_revids = rev.parent_ids
271
def add_object(self, obj, ie, path):
272
if obj.type_name == "commit":
274
assert type(ie) is dict
276
type_data = (self.revid, self._commit.tree, ie)
277
self.cache.idmap._by_revid[self.revid] = obj.id
278
elif obj.type_name in ("blob", "tree"):
280
if obj.type_name == "blob":
281
revision = ie.revision
283
revision = self.revid
284
key = type_data = (ie.file_id, revision)
285
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
288
entry = (obj.type_name, type_data)
289
self.cache.idmap._by_sha.setdefault(obj.id, {})[key] = entry
292
if self._commit is None:
293
raise AssertionError("No commit object added")
297
class DictGitShaMap(GitShaMap):
298
"""Git SHA map that uses a dictionary."""
305
def lookup_blob_id(self, fileid, revision):
306
return self._by_fileid[revision][fileid]
308
def lookup_git_sha(self, sha):
309
for entry in self._by_sha[sha].itervalues():
312
def lookup_tree_id(self, fileid, revision):
313
return self._by_fileid[revision][fileid]
315
def lookup_commit(self, revid):
316
return self._by_revid[revid]
319
for key, entries in self._by_sha.iteritems():
320
for (type, type_data) in entries.values():
325
return self._by_sha.iterkeys()
328
class SqliteCacheUpdater(CacheUpdater):
330
def __init__(self, cache, rev):
332
self.db = self.cache.idmap.db
333
self.revid = rev.revision_id
338
def add_object(self, obj, ie, path):
339
if obj.type_name == "commit":
341
self._testament3_sha1 = ie["testament3-sha1"]
342
assert type(ie) is dict
343
elif obj.type_name == "tree":
345
self._trees.append((obj.id, ie.file_id, self.revid))
346
elif obj.type_name == "blob":
348
self._blobs.append((obj.id, ie.file_id, ie.revision))
353
if self._commit is None:
354
raise AssertionError("No commit object added")
356
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
359
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
362
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
363
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
367
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
370
class SqliteGitCacheFormat(BzrGitCacheFormat):
372
def get_format_string(self):
373
return 'bzr-git sha map version 1 using sqlite\n'
375
def open(self, transport):
377
basepath = transport.local_abspath(".")
378
except bzrlib.errors.NotLocalUrl:
379
basepath = get_cache_dir()
380
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
383
class SqliteGitShaMap(GitShaMap):
384
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
386
def __init__(self, path=None):
389
self.db = sqlite3.connect(":memory:")
391
if not mapdbs().has_key(path):
392
mapdbs()[path] = sqlite3.connect(path)
393
self.db = mapdbs()[path]
394
self.db.text_factory = str
395
self.db.executescript("""
396
create table if not exists commits(
397
sha1 text not null check(length(sha1) == 40),
399
tree_sha text not null check(length(tree_sha) == 40)
401
create index if not exists commit_sha1 on commits(sha1);
402
create unique index if not exists commit_revid on commits(revid);
403
create table if not exists blobs(
404
sha1 text not null check(length(sha1) == 40),
405
fileid text not null,
408
create index if not exists blobs_sha1 on blobs(sha1);
409
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
410
create table if not exists trees(
411
sha1 text unique not null check(length(sha1) == 40),
412
fileid text not null,
415
create unique index if not exists trees_sha1 on trees(sha1);
416
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
419
self.db.executescript(
420
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
421
except sqlite3.OperationalError:
422
pass # Column already exists.
425
return "%s(%r)" % (self.__class__.__name__, self.path)
427
def lookup_commit(self, revid):
428
cursor = self.db.execute("select sha1 from commits where revid = ?",
430
row = cursor.fetchone()
435
def commit_write_group(self):
438
def lookup_blob_id(self, fileid, revision):
439
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
442
raise KeyError(fileid)
444
def lookup_tree_id(self, fileid, revision):
445
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
448
raise KeyError(fileid)
450
def lookup_git_sha(self, sha):
451
"""Lookup a Git sha in the database.
453
:param sha: Git object sha
454
:return: (type, type_data) with type_data:
455
commit: revid, tree sha, verifiers
460
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
461
for row in cursor.fetchall():
463
yield ("commit", (row[0], row[1], {"testament3-sha1": row[2]}))
464
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
465
for row in cursor.fetchall():
468
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
469
for row in cursor.fetchall():
476
"""List the revision ids known."""
477
return (row for (row,) in self.db.execute("select revid from commits"))
480
"""List the SHA1s."""
481
for table in ("blobs", "commits", "trees"):
482
for (sha,) in self.db.execute("select sha1 from %s" % table):
486
class TdbCacheUpdater(CacheUpdater):
487
"""Cache updater for tdb-based caches."""
489
def __init__(self, cache, rev):
491
self.db = cache.idmap.db
492
self.revid = rev.revision_id
493
self.parent_revids = rev.parent_ids
497
def add_object(self, obj, ie, path):
498
sha = obj.sha().digest()
499
if obj.type_name == "commit":
500
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
501
assert type(ie) is dict, "was %r" % ie
502
type_data = (self.revid, obj.tree, ie["testament3-sha1"])
504
elif obj.type_name == "blob":
507
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
508
type_data = (ie.file_id, ie.revision)
509
elif obj.type_name == "tree":
512
type_data = (ie.file_id, self.revid)
515
entry = "\0".join((obj.type_name, ) + type_data) + "\n"
518
oldval = self.db[key]
522
if oldval[-1] != "\n":
523
self.db[key] = "".join([oldval, "\n", entry])
525
self.db[key] = "".join([oldval, entry])
528
if self._commit is None:
529
raise AssertionError("No commit object added")
533
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
536
class TdbGitCacheFormat(BzrGitCacheFormat):
537
"""Cache format for tdb-based caches."""
539
def get_format_string(self):
540
return 'bzr-git sha map version 3 using tdb\n'
542
def open(self, transport):
544
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
545
except bzrlib.errors.NotLocalUrl:
546
basepath = get_cache_dir()
547
assert isinstance(basepath, str)
549
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
552
"Unable to open existing bzr-git cache because 'tdb' is not "
556
class TdbGitShaMap(GitShaMap):
557
"""SHA Map that uses a TDB database.
561
"git <sha1>" -> "<type> <type-data1> <type-data2>"
562
"commit revid" -> "<sha1> <tree-id>"
563
"tree fileid revid" -> "<sha1>"
564
"blob fileid revid" -> "<sha1>"
568
TDB_HASH_SIZE = 50000
570
def __init__(self, path=None):
576
assert isinstance(path, str)
577
if not mapdbs().has_key(path):
578
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
579
os.O_RDWR|os.O_CREAT)
580
self.db = mapdbs()[path]
582
if int(self.db["version"]) not in (2, 3):
583
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
584
self.db["version"], self.TDB_MAP_VERSION)
588
self.db["version"] = str(self.TDB_MAP_VERSION)
590
def start_write_group(self):
591
"""Start writing changes."""
592
self.db.transaction_start()
594
def commit_write_group(self):
595
"""Commit any pending changes."""
596
self.db.transaction_commit()
598
def abort_write_group(self):
599
"""Abort any pending changes."""
600
self.db.transaction_cancel()
603
return "%s(%r)" % (self.__class__.__name__, self.path)
605
def lookup_commit(self, revid):
606
return sha_to_hex(self.db["commit\0" + revid][:20])
608
def lookup_blob_id(self, fileid, revision):
609
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
611
def lookup_git_sha(self, sha):
612
"""Lookup a Git sha in the database.
614
:param sha: Git object sha
615
:return: (type, type_data) with type_data:
616
commit: revid, tree sha
621
sha = hex_to_sha(sha)
622
value = self.db["git\0" + sha]
623
for data in value.splitlines():
624
data = data.split("\0")
625
if data[0] == "commit":
627
yield (data[0], (data[1], data[2], {}))
629
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
630
elif data[0] in ("tree", "blob"):
631
yield (data[0], tuple(data[1:]))
633
raise AssertionError("unknown type %r" % data[0])
635
def missing_revisions(self, revids):
638
if self.db.get("commit\0" + revid) is None:
643
"""List the revision ids known."""
644
for key in self.db.iterkeys():
645
if key.startswith("commit\0"):
649
"""List the SHA1s."""
650
for key in self.db.iterkeys():
651
if key.startswith("git\0"):
652
yield sha_to_hex(key[4:])
655
class VersionedFilesContentCache(ContentCache):
657
def __init__(self, vf):
661
self._vf.insert_record_stream(
662
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
663
obj.as_legacy_object_chunks())])
665
def __getitem__(self, sha):
666
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
667
entry = stream.next()
668
if entry.storage_kind == 'absent':
670
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
673
class GitObjectStoreContentCache(ContentCache):
675
def __init__(self, store):
678
def add_multi(self, objs):
679
self.store.add_objects(objs)
681
def add(self, obj, path):
682
self.store.add_object(obj)
684
def __getitem__(self, sha):
685
return self.store[sha]
688
class IndexCacheUpdater(CacheUpdater):
690
def __init__(self, cache, rev):
692
self.revid = rev.revision_id
693
self.parent_revids = rev.parent_ids
696
self._cache_objs = set()
698
def add_object(self, obj, ie, path):
699
if obj.type_name == "commit":
701
assert type(ie) is dict
702
self.cache.idmap._add_git_sha(obj.id, "commit",
703
(self.revid, obj.tree, ie))
704
self.cache.idmap._add_node(("commit", self.revid, "X"),
705
" ".join((obj.id, obj.tree)))
706
self._cache_objs.add((obj, path))
707
elif obj.type_name == "blob":
708
self.cache.idmap._add_git_sha(obj.id, "blob",
709
(ie.file_id, ie.revision))
710
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
711
if ie.kind == "symlink":
712
self._cache_objs.add((obj, path))
713
elif obj.type_name == "tree":
714
self.cache.idmap._add_git_sha(obj.id, "tree",
715
(ie.file_id, self.revid))
716
self._cache_objs.add((obj, path))
721
self.cache.content_cache.add_multi(self._cache_objs)
725
class IndexBzrGitCache(BzrGitCache):
727
def __init__(self, transport=None):
728
mapper = versionedfile.ConstantMapper("trees")
729
shamap = IndexGitShaMap(transport.clone('index'))
730
#trees_store = knit.make_file_factory(True, mapper)(transport)
731
#content_cache = VersionedFilesContentCache(trees_store)
732
from bzrlib.plugins.git.transportgit import TransportObjectStore
733
store = TransportObjectStore(transport.clone('objects'))
734
content_cache = GitObjectStoreContentCache(store)
735
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
739
class IndexGitCacheFormat(BzrGitCacheFormat):
741
def get_format_string(self):
742
return 'bzr-git sha map with git object cache version 1\n'
744
def initialize(self, transport):
745
super(IndexGitCacheFormat, self).initialize(transport)
746
transport.mkdir('index')
747
transport.mkdir('objects')
748
from bzrlib.plugins.git.transportgit import TransportObjectStore
749
TransportObjectStore.init(transport.clone('objects'))
751
def open(self, transport):
752
return IndexBzrGitCache(transport)
755
class IndexGitShaMap(GitShaMap):
756
"""SHA Map that uses the Bazaar APIs to store a cache.
758
BTree Index file with the following contents:
760
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
761
("commit", <revid>) -> "<sha1> <tree-id>"
762
("blob", <fileid>, <revid>) -> <sha1>
766
def __init__(self, transport=None):
767
if transport is None:
768
self._transport = None
769
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
770
self._builder = self._index
773
self._transport = transport
774
self._index = _mod_index.CombinedGraphIndex([])
775
for name in self._transport.list_dir("."):
776
if not name.endswith(".rix"):
778
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
779
self._transport.stat(name).st_size)
780
self._index.insert_index(0, x)
783
def from_repository(cls, repository):
784
transport = getattr(repository, "_transport", None)
785
if transport is not None:
787
transport.mkdir('git')
788
except bzrlib.errors.FileExists:
790
return cls(transport.clone('git'))
791
from bzrlib.transport import get_transport
792
return cls(get_transport(get_cache_dir()))
795
if self._transport is not None:
796
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
798
return "%s()" % (self.__class__.__name__)
801
assert self._builder is None
802
self.start_write_group()
803
for _, key, value in self._index.iter_all_entries():
804
self._builder.add_node(key, value)
806
for name in self._transport.list_dir('.'):
807
if name.endswith('.rix'):
808
to_remove.append(name)
809
self.commit_write_group()
810
del self._index.indices[1:]
811
for name in to_remove:
812
self._transport.rename(name, name + '.old')
814
def start_write_group(self):
815
assert self._builder is None
816
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
817
self._name = osutils.sha()
819
def commit_write_group(self):
820
assert self._builder is not None
821
stream = self._builder.finish()
822
name = self._name.hexdigest() + ".rix"
823
size = self._transport.put_file(name, stream)
824
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
825
self._index.insert_index(0, index)
829
def abort_write_group(self):
830
assert self._builder is not None
834
def _add_node(self, key, value):
836
self._builder.add_node(key, value)
837
except bzrlib.errors.BadIndexDuplicateKey:
838
# Multiple bzr objects can have the same contents
843
def _get_entry(self, key):
844
entries = self._index.iter_entries([key])
846
return entries.next()[2]
847
except StopIteration:
848
if self._builder is None:
850
entries = self._builder.iter_entries([key])
852
return entries.next()[2]
853
except StopIteration:
856
def _iter_entries_prefix(self, prefix):
857
for entry in self._index.iter_entries_prefix([prefix]):
858
yield (entry[1], entry[2])
859
if self._builder is not None:
860
for entry in self._builder.iter_entries_prefix([prefix]):
861
yield (entry[1], entry[2])
863
def lookup_commit(self, revid):
864
return self._get_entry(("commit", revid, "X"))[:40]
866
def _add_git_sha(self, hexsha, type, type_data):
867
if hexsha is not None:
868
self._name.update(hexsha)
870
td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
873
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
875
# This object is not represented in Git - perhaps an empty
877
self._name.update(type + " ".join(type_data))
879
def lookup_blob_id(self, fileid, revision):
880
return self._get_entry(("blob", fileid, revision))
882
def lookup_git_sha(self, sha):
884
sha = sha_to_hex(sha)
886
for key, value in self._iter_entries_prefix(("git", sha, None)):
888
data = value.split(" ", 3)
889
if data[0] == "commit":
890
yield ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
892
yield (data[0], tuple(data[1:]))
897
"""List the revision ids known."""
898
for key, value in self._iter_entries_prefix(("commit", None, None)):
901
def missing_revisions(self, revids):
902
"""Return set of all the revisions that are not present."""
903
missing_revids = set(revids)
904
for _, key, value in self._index.iter_entries((
905
("commit", revid, "X") for revid in revids)):
906
missing_revids.remove(key[1])
907
return missing_revids
910
"""List the SHA1s."""
911
for key, value in self._iter_entries_prefix(("git", None, None)):
915
formats = registry.Registry()
916
formats.register(TdbGitCacheFormat().get_format_string(),
918
formats.register(SqliteGitCacheFormat().get_format_string(),
919
SqliteGitCacheFormat())
920
formats.register(IndexGitCacheFormat().get_format_string(),
921
IndexGitCacheFormat())
922
# In the future, this will become the default:
923
# formats.register('default', IndexGitCacheFormat())
927
formats.register('default', SqliteGitCacheFormat())
929
formats.register('default', TdbGitCacheFormat())
933
def migrate_ancient_formats(repo_transport):
934
# Prefer migrating git.db over git.tdb, since the latter may not
935
# be openable on some platforms.
936
if repo_transport.has("git.db"):
937
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
938
repo_transport.rename("git.db", "git/idmap.db")
939
elif repo_transport.has("git.tdb"):
940
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
941
repo_transport.rename("git.tdb", "git/idmap.tdb")
944
def remove_readonly_transport_decorator(transport):
945
if transport.is_readonly():
946
return transport._decorated
950
def from_repository(repository):
951
"""Open a cache file for a repository.
953
If the repository is remote and there is no transport available from it
954
this will use a local file in the users cache directory
955
(typically ~/.cache/bazaar/git/)
957
:param repository: A repository object
959
repo_transport = getattr(repository, "_transport", None)
960
if repo_transport is not None:
961
# Migrate older cache formats
962
repo_transport = remove_readonly_transport_decorator(repo_transport)
964
repo_transport.mkdir("git")
965
except bzrlib.errors.FileExists:
968
migrate_ancient_formats(repo_transport)
969
return BzrGitCacheFormat.from_repository(repository)