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.get("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
if row[2] is not None:
464
verifiers = {"testament3-sha1": row[2]}
467
yield ("commit", (row[0], row[1], verifiers))
468
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
469
for row in cursor.fetchall():
472
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
473
for row in cursor.fetchall():
480
"""List the revision ids known."""
481
return (row for (row,) in self.db.execute("select revid from commits"))
484
"""List the SHA1s."""
485
for table in ("blobs", "commits", "trees"):
486
for (sha,) in self.db.execute("select sha1 from %s" % table):
490
class TdbCacheUpdater(CacheUpdater):
491
"""Cache updater for tdb-based caches."""
493
def __init__(self, cache, rev):
495
self.db = cache.idmap.db
496
self.revid = rev.revision_id
497
self.parent_revids = rev.parent_ids
501
def add_object(self, obj, ie, path):
502
sha = obj.sha().digest()
503
if obj.type_name == "commit":
504
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
505
assert type(ie) is dict, "was %r" % ie
506
type_data = (self.revid, obj.tree)
508
type_data += (ie["testament3-sha1"],)
512
elif obj.type_name == "blob":
515
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
516
type_data = (ie.file_id, ie.revision)
517
elif obj.type_name == "tree":
520
type_data = (ie.file_id, self.revid)
523
entry = "\0".join((obj.type_name, ) + type_data) + "\n"
526
oldval = self.db[key]
530
if oldval[-1] != "\n":
531
self.db[key] = "".join([oldval, "\n", entry])
533
self.db[key] = "".join([oldval, entry])
536
if self._commit is None:
537
raise AssertionError("No commit object added")
541
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
544
class TdbGitCacheFormat(BzrGitCacheFormat):
545
"""Cache format for tdb-based caches."""
547
def get_format_string(self):
548
return 'bzr-git sha map version 3 using tdb\n'
550
def open(self, transport):
552
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
553
except bzrlib.errors.NotLocalUrl:
554
basepath = get_cache_dir()
555
assert isinstance(basepath, str)
557
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
560
"Unable to open existing bzr-git cache because 'tdb' is not "
564
class TdbGitShaMap(GitShaMap):
565
"""SHA Map that uses a TDB database.
569
"git <sha1>" -> "<type> <type-data1> <type-data2>"
570
"commit revid" -> "<sha1> <tree-id>"
571
"tree fileid revid" -> "<sha1>"
572
"blob fileid revid" -> "<sha1>"
576
TDB_HASH_SIZE = 50000
578
def __init__(self, path=None):
584
assert isinstance(path, str)
585
if not mapdbs().has_key(path):
586
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
587
os.O_RDWR|os.O_CREAT)
588
self.db = mapdbs()[path]
590
if int(self.db["version"]) not in (2, 3):
591
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
592
self.db["version"], self.TDB_MAP_VERSION)
596
self.db["version"] = str(self.TDB_MAP_VERSION)
598
def start_write_group(self):
599
"""Start writing changes."""
600
self.db.transaction_start()
602
def commit_write_group(self):
603
"""Commit any pending changes."""
604
self.db.transaction_commit()
606
def abort_write_group(self):
607
"""Abort any pending changes."""
608
self.db.transaction_cancel()
611
return "%s(%r)" % (self.__class__.__name__, self.path)
613
def lookup_commit(self, revid):
614
return sha_to_hex(self.db["commit\0" + revid][:20])
616
def lookup_blob_id(self, fileid, revision):
617
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
619
def lookup_git_sha(self, sha):
620
"""Lookup a Git sha in the database.
622
:param sha: Git object sha
623
:return: (type, type_data) with type_data:
624
commit: revid, tree sha
629
sha = hex_to_sha(sha)
630
value = self.db["git\0" + sha]
631
for data in value.splitlines():
632
data = data.split("\0")
633
if data[0] == "commit":
635
yield (data[0], (data[1], data[2], {}))
637
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
638
elif data[0] in ("tree", "blob"):
639
yield (data[0], tuple(data[1:]))
641
raise AssertionError("unknown type %r" % data[0])
643
def missing_revisions(self, revids):
646
if self.db.get("commit\0" + revid) is None:
651
"""List the revision ids known."""
652
for key in self.db.iterkeys():
653
if key.startswith("commit\0"):
657
"""List the SHA1s."""
658
for key in self.db.iterkeys():
659
if key.startswith("git\0"):
660
yield sha_to_hex(key[4:])
663
class VersionedFilesContentCache(ContentCache):
665
def __init__(self, vf):
669
self._vf.insert_record_stream(
670
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
671
obj.as_legacy_object_chunks())])
673
def __getitem__(self, sha):
674
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
675
entry = stream.next()
676
if entry.storage_kind == 'absent':
678
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
681
class GitObjectStoreContentCache(ContentCache):
683
def __init__(self, store):
686
def add_multi(self, objs):
687
self.store.add_objects(objs)
689
def add(self, obj, path):
690
self.store.add_object(obj)
692
def __getitem__(self, sha):
693
return self.store[sha]
696
class IndexCacheUpdater(CacheUpdater):
698
def __init__(self, cache, rev):
700
self.revid = rev.revision_id
701
self.parent_revids = rev.parent_ids
704
self._cache_objs = set()
706
def add_object(self, obj, ie, path):
707
if obj.type_name == "commit":
709
assert type(ie) is dict
710
self.cache.idmap._add_git_sha(obj.id, "commit",
711
(self.revid, obj.tree, ie))
712
self.cache.idmap._add_node(("commit", self.revid, "X"),
713
" ".join((obj.id, obj.tree)))
714
self._cache_objs.add((obj, path))
715
elif obj.type_name == "blob":
716
self.cache.idmap._add_git_sha(obj.id, "blob",
717
(ie.file_id, ie.revision))
718
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
719
if ie.kind == "symlink":
720
self._cache_objs.add((obj, path))
721
elif obj.type_name == "tree":
722
self.cache.idmap._add_git_sha(obj.id, "tree",
723
(ie.file_id, self.revid))
724
self._cache_objs.add((obj, path))
729
self.cache.content_cache.add_multi(self._cache_objs)
733
class IndexBzrGitCache(BzrGitCache):
735
def __init__(self, transport=None):
736
mapper = versionedfile.ConstantMapper("trees")
737
shamap = IndexGitShaMap(transport.clone('index'))
738
#trees_store = knit.make_file_factory(True, mapper)(transport)
739
#content_cache = VersionedFilesContentCache(trees_store)
740
from bzrlib.plugins.git.transportgit import TransportObjectStore
741
store = TransportObjectStore(transport.clone('objects'))
742
content_cache = GitObjectStoreContentCache(store)
743
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
747
class IndexGitCacheFormat(BzrGitCacheFormat):
749
def get_format_string(self):
750
return 'bzr-git sha map with git object cache version 1\n'
752
def initialize(self, transport):
753
super(IndexGitCacheFormat, self).initialize(transport)
754
transport.mkdir('index')
755
transport.mkdir('objects')
756
from bzrlib.plugins.git.transportgit import TransportObjectStore
757
TransportObjectStore.init(transport.clone('objects'))
759
def open(self, transport):
760
return IndexBzrGitCache(transport)
763
class IndexGitShaMap(GitShaMap):
764
"""SHA Map that uses the Bazaar APIs to store a cache.
766
BTree Index file with the following contents:
768
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
769
("commit", <revid>) -> "<sha1> <tree-id>"
770
("blob", <fileid>, <revid>) -> <sha1>
774
def __init__(self, transport=None):
775
if transport is None:
776
self._transport = None
777
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
778
self._builder = self._index
781
self._transport = transport
782
self._index = _mod_index.CombinedGraphIndex([])
783
for name in self._transport.list_dir("."):
784
if not name.endswith(".rix"):
786
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
787
self._transport.stat(name).st_size)
788
self._index.insert_index(0, x)
791
def from_repository(cls, repository):
792
transport = getattr(repository, "_transport", None)
793
if transport is not None:
795
transport.mkdir('git')
796
except bzrlib.errors.FileExists:
798
return cls(transport.clone('git'))
799
from bzrlib.transport import get_transport
800
return cls(get_transport(get_cache_dir()))
803
if self._transport is not None:
804
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
806
return "%s()" % (self.__class__.__name__)
809
assert self._builder is None
810
self.start_write_group()
811
for _, key, value in self._index.iter_all_entries():
812
self._builder.add_node(key, value)
814
for name in self._transport.list_dir('.'):
815
if name.endswith('.rix'):
816
to_remove.append(name)
817
self.commit_write_group()
818
del self._index.indices[1:]
819
for name in to_remove:
820
self._transport.rename(name, name + '.old')
822
def start_write_group(self):
823
assert self._builder is None
824
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
825
self._name = osutils.sha()
827
def commit_write_group(self):
828
assert self._builder is not None
829
stream = self._builder.finish()
830
name = self._name.hexdigest() + ".rix"
831
size = self._transport.put_file(name, stream)
832
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
833
self._index.insert_index(0, index)
837
def abort_write_group(self):
838
assert self._builder is not None
842
def _add_node(self, key, value):
844
self._builder.add_node(key, value)
845
except bzrlib.errors.BadIndexDuplicateKey:
846
# Multiple bzr objects can have the same contents
851
def _get_entry(self, key):
852
entries = self._index.iter_entries([key])
854
return entries.next()[2]
855
except StopIteration:
856
if self._builder is None:
858
entries = self._builder.iter_entries([key])
860
return entries.next()[2]
861
except StopIteration:
864
def _iter_entries_prefix(self, prefix):
865
for entry in self._index.iter_entries_prefix([prefix]):
866
yield (entry[1], entry[2])
867
if self._builder is not None:
868
for entry in self._builder.iter_entries_prefix([prefix]):
869
yield (entry[1], entry[2])
871
def lookup_commit(self, revid):
872
return self._get_entry(("commit", revid, "X"))[:40]
874
def _add_git_sha(self, hexsha, type, type_data):
875
if hexsha is not None:
876
self._name.update(hexsha)
878
td = (type_data[0], type_data[1])
880
td += (type_data[2]["testament3-sha1"],)
885
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
887
# This object is not represented in Git - perhaps an empty
889
self._name.update(type + " ".join(type_data))
891
def lookup_blob_id(self, fileid, revision):
892
return self._get_entry(("blob", fileid, revision))
894
def lookup_git_sha(self, sha):
896
sha = sha_to_hex(sha)
898
for key, value in self._iter_entries_prefix(("git", sha, None)):
900
data = value.split(" ", 3)
901
if data[0] == "commit":
903
verifiers = {"testament3-sha1": data[3]}
906
yield ("commit", (data[1], data[2], verifiers))
908
yield (data[0], tuple(data[1:]))
913
"""List the revision ids known."""
914
for key, value in self._iter_entries_prefix(("commit", None, None)):
917
def missing_revisions(self, revids):
918
"""Return set of all the revisions that are not present."""
919
missing_revids = set(revids)
920
for _, key, value in self._index.iter_entries((
921
("commit", revid, "X") for revid in revids)):
922
missing_revids.remove(key[1])
923
return missing_revids
926
"""List the SHA1s."""
927
for key, value in self._iter_entries_prefix(("git", None, None)):
931
formats = registry.Registry()
932
formats.register(TdbGitCacheFormat().get_format_string(),
934
formats.register(SqliteGitCacheFormat().get_format_string(),
935
SqliteGitCacheFormat())
936
formats.register(IndexGitCacheFormat().get_format_string(),
937
IndexGitCacheFormat())
938
# In the future, this will become the default:
939
# formats.register('default', IndexGitCacheFormat())
943
formats.register('default', SqliteGitCacheFormat())
945
formats.register('default', TdbGitCacheFormat())
949
def migrate_ancient_formats(repo_transport):
950
# Prefer migrating git.db over git.tdb, since the latter may not
951
# be openable on some platforms.
952
if repo_transport.has("git.db"):
953
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
954
repo_transport.rename("git.db", "git/idmap.db")
955
elif repo_transport.has("git.tdb"):
956
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
957
repo_transport.rename("git.tdb", "git/idmap.tdb")
960
def remove_readonly_transport_decorator(transport):
961
if transport.is_readonly():
962
return transport._decorated
966
def from_repository(repository):
967
"""Open a cache file for a repository.
969
If the repository is remote and there is no transport available from it
970
this will use a local file in the users cache directory
971
(typically ~/.cache/bazaar/git/)
973
:param repository: A repository object
975
repo_transport = getattr(repository, "_transport", None)
976
if repo_transport is not None:
977
# Migrate older cache formats
978
repo_transport = remove_readonly_transport_decorator(repo_transport)
980
repo_transport.mkdir("git")
981
except bzrlib.errors.FileExists:
984
migrate_ancient_formats(repo_transport)
985
return BzrGitCacheFormat.from_repository(repository)