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: (type, type_data) 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
275
type_data = (self.revid, self._commit.tree, ie)
276
self.cache.idmap._by_revid[self.revid] = obj.id
277
elif obj.type_name in ("blob", "tree"):
279
if obj.type_name == "blob":
280
revision = ie.revision
282
revision = self.revid
283
type_data = (ie.file_id, revision)
284
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
287
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
290
if self._commit is None:
291
raise AssertionError("No commit object added")
295
class DictGitShaMap(GitShaMap):
296
"""Git SHA map that uses a dictionary."""
303
def lookup_blob_id(self, fileid, revision):
304
return self._by_fileid[revision][fileid]
306
def lookup_git_sha(self, sha):
307
return self._by_sha[sha]
309
def lookup_tree_id(self, fileid, revision):
310
return self._by_fileid[revision][fileid]
312
def lookup_commit(self, revid):
313
return self._by_revid[revid]
316
for key, (type, type_data) in self._by_sha.iteritems():
321
return self._by_sha.iterkeys()
324
class SqliteCacheUpdater(CacheUpdater):
326
def __init__(self, cache, rev):
328
self.db = self.cache.idmap.db
329
self.revid = rev.revision_id
334
def add_object(self, obj, ie, path):
335
if obj.type_name == "commit":
337
self._testament3_sha1 = ie["testament3-sha1"]
338
assert type(ie) is dict
339
elif obj.type_name == "tree":
341
self._trees.append((obj.id, ie.file_id, self.revid))
342
elif obj.type_name == "blob":
344
self._blobs.append((obj.id, ie.file_id, ie.revision))
349
if self._commit is None:
350
raise AssertionError("No commit object added")
352
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
355
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
358
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
359
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
363
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
366
class SqliteGitCacheFormat(BzrGitCacheFormat):
368
def get_format_string(self):
369
return 'bzr-git sha map version 1 using sqlite\n'
371
def open(self, transport):
373
basepath = transport.local_abspath(".")
374
except bzrlib.errors.NotLocalUrl:
375
basepath = get_cache_dir()
376
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
379
class SqliteGitShaMap(GitShaMap):
380
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
382
def __init__(self, path=None):
385
self.db = sqlite3.connect(":memory:")
387
if not mapdbs().has_key(path):
388
mapdbs()[path] = sqlite3.connect(path)
389
self.db = mapdbs()[path]
390
self.db.text_factory = str
391
self.db.executescript("""
392
create table if not exists commits(
393
sha1 text not null check(length(sha1) == 40),
395
tree_sha text not null check(length(tree_sha) == 40)
397
create index if not exists commit_sha1 on commits(sha1);
398
create unique index if not exists commit_revid on commits(revid);
399
create table if not exists blobs(
400
sha1 text not null check(length(sha1) == 40),
401
fileid text not null,
404
create index if not exists blobs_sha1 on blobs(sha1);
405
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
406
create table if not exists trees(
407
sha1 text unique not null check(length(sha1) == 40),
408
fileid text not null,
411
create unique index if not exists trees_sha1 on trees(sha1);
412
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
415
self.db.executescript(
416
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
417
except sqlite3.OperationalError:
418
pass # Column already exists.
421
return "%s(%r)" % (self.__class__.__name__, self.path)
423
def lookup_commit(self, revid):
424
cursor = self.db.execute("select sha1 from commits where revid = ?",
426
row = cursor.fetchone()
431
def commit_write_group(self):
434
def lookup_blob_id(self, fileid, revision):
435
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
438
raise KeyError(fileid)
440
def lookup_tree_id(self, fileid, revision):
441
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
444
raise KeyError(fileid)
446
def lookup_git_sha(self, sha):
447
"""Lookup a Git sha in the database.
449
:param sha: Git object sha
450
:return: (type, type_data) with type_data:
451
commit: revid, tree sha, verifiers
455
row = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,)).fetchone()
457
return ("commit", (row[0], row[1], {"testament3-sha1": row[2]}))
458
row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
461
row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
467
"""List the revision ids known."""
468
return (row for (row,) in self.db.execute("select revid from commits"))
471
"""List the SHA1s."""
472
for table in ("blobs", "commits", "trees"):
473
for (sha,) in self.db.execute("select sha1 from %s" % table):
477
class TdbCacheUpdater(CacheUpdater):
478
"""Cache updater for tdb-based caches."""
480
def __init__(self, cache, rev):
482
self.db = cache.idmap.db
483
self.revid = rev.revision_id
484
self.parent_revids = rev.parent_ids
488
def add_object(self, obj, ie, path):
489
sha = obj.sha().digest()
490
if obj.type_name == "commit":
491
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
492
assert type(ie) is dict, "was %r" % ie
493
type_data = (self.revid, obj.tree, ie["testament3-sha1"])
495
elif obj.type_name == "blob":
498
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
499
type_data = (ie.file_id, ie.revision)
500
elif obj.type_name == "tree":
503
type_data = (ie.file_id, self.revid)
506
self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
509
if self._commit is None:
510
raise AssertionError("No commit object added")
514
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
517
class TdbGitCacheFormat(BzrGitCacheFormat):
518
"""Cache format for tdb-based caches."""
520
def get_format_string(self):
521
return 'bzr-git sha map version 3 using tdb\n'
523
def open(self, transport):
525
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
526
except bzrlib.errors.NotLocalUrl:
527
basepath = get_cache_dir()
528
assert isinstance(basepath, str)
530
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
533
"Unable to open existing bzr-git cache because 'tdb' is not "
537
class TdbGitShaMap(GitShaMap):
538
"""SHA Map that uses a TDB database.
542
"git <sha1>" -> "<type> <type-data1> <type-data2>"
543
"commit revid" -> "<sha1> <tree-id>"
544
"tree fileid revid" -> "<sha1>"
545
"blob fileid revid" -> "<sha1>"
549
TDB_HASH_SIZE = 50000
551
def __init__(self, path=None):
557
assert isinstance(path, str)
558
if not mapdbs().has_key(path):
559
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
560
os.O_RDWR|os.O_CREAT)
561
self.db = mapdbs()[path]
563
if int(self.db["version"]) not in (2, 3):
564
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
565
self.db["version"], self.TDB_MAP_VERSION)
569
self.db["version"] = str(self.TDB_MAP_VERSION)
571
def start_write_group(self):
572
"""Start writing changes."""
573
self.db.transaction_start()
575
def commit_write_group(self):
576
"""Commit any pending changes."""
577
self.db.transaction_commit()
579
def abort_write_group(self):
580
"""Abort any pending changes."""
581
self.db.transaction_cancel()
584
return "%s(%r)" % (self.__class__.__name__, self.path)
586
def lookup_commit(self, revid):
587
return sha_to_hex(self.db["commit\0" + revid][:20])
589
def lookup_blob_id(self, fileid, revision):
590
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
592
def lookup_git_sha(self, sha):
593
"""Lookup a Git sha in the database.
595
:param sha: Git object sha
596
:return: (type, type_data) with type_data:
597
commit: revid, tree sha
602
sha = hex_to_sha(sha)
603
data = self.db["git\0" + sha].split("\0")
604
if data[0] == "commit":
606
return (data[0], (data[1], data[2], {}))
608
return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
610
return (data[0], tuple(data[1:]))
612
def missing_revisions(self, revids):
615
if self.db.get("commit\0" + revid) is None:
620
"""List the revision ids known."""
621
for key in self.db.iterkeys():
622
if key.startswith("commit\0"):
626
"""List the SHA1s."""
627
for key in self.db.iterkeys():
628
if key.startswith("git\0"):
629
yield sha_to_hex(key[4:])
632
class VersionedFilesContentCache(ContentCache):
634
def __init__(self, vf):
638
self._vf.insert_record_stream(
639
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
640
obj.as_legacy_object_chunks())])
642
def __getitem__(self, sha):
643
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
644
entry = stream.next()
645
if entry.storage_kind == 'absent':
647
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
650
class GitObjectStoreContentCache(ContentCache):
652
def __init__(self, store):
655
def add_multi(self, objs):
656
self.store.add_objects(objs)
658
def add(self, obj, path):
659
self.store.add_object(obj)
661
def __getitem__(self, sha):
662
return self.store[sha]
665
class IndexCacheUpdater(CacheUpdater):
667
def __init__(self, cache, rev):
669
self.revid = rev.revision_id
670
self.parent_revids = rev.parent_ids
673
self._cache_objs = set()
675
def add_object(self, obj, ie, path):
676
if obj.type_name == "commit":
678
assert type(ie) is dict
679
self.cache.idmap._add_git_sha(obj.id, "commit",
680
(self.revid, obj.tree, ie))
681
self.cache.idmap._add_node(("commit", self.revid, "X"),
682
" ".join((obj.id, obj.tree)))
683
self._cache_objs.add((obj, path))
684
elif obj.type_name == "blob":
685
self.cache.idmap._add_git_sha(obj.id, "blob",
686
(ie.file_id, ie.revision))
687
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
688
if ie.kind == "symlink":
689
self._cache_objs.add((obj, path))
690
elif obj.type_name == "tree":
691
self.cache.idmap._add_git_sha(obj.id, "tree",
692
(ie.file_id, self.revid))
693
self._cache_objs.add((obj, path))
698
self.cache.content_cache.add_multi(self._cache_objs)
702
class IndexBzrGitCache(BzrGitCache):
704
def __init__(self, transport=None):
705
mapper = versionedfile.ConstantMapper("trees")
706
shamap = IndexGitShaMap(transport.clone('index'))
707
#trees_store = knit.make_file_factory(True, mapper)(transport)
708
#content_cache = VersionedFilesContentCache(trees_store)
709
from bzrlib.plugins.git.transportgit import TransportObjectStore
710
store = TransportObjectStore(transport.clone('objects'))
711
content_cache = GitObjectStoreContentCache(store)
712
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
716
class IndexGitCacheFormat(BzrGitCacheFormat):
718
def get_format_string(self):
719
return 'bzr-git sha map with git object cache version 1\n'
721
def initialize(self, transport):
722
super(IndexGitCacheFormat, self).initialize(transport)
723
transport.mkdir('index')
724
transport.mkdir('objects')
725
from bzrlib.plugins.git.transportgit import TransportObjectStore
726
TransportObjectStore.init(transport.clone('objects'))
728
def open(self, transport):
729
return IndexBzrGitCache(transport)
732
class IndexGitShaMap(GitShaMap):
733
"""SHA Map that uses the Bazaar APIs to store a cache.
735
BTree Index file with the following contents:
737
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
738
("commit", <revid>) -> "<sha1> <tree-id>"
739
("blob", <fileid>, <revid>) -> <sha1>
743
def __init__(self, transport=None):
744
if transport is None:
745
self._transport = None
746
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
747
self._builder = self._index
750
self._transport = transport
751
self._index = _mod_index.CombinedGraphIndex([])
752
for name in self._transport.list_dir("."):
753
if not name.endswith(".rix"):
755
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
756
self._transport.stat(name).st_size)
757
self._index.insert_index(0, x)
760
def from_repository(cls, repository):
761
transport = getattr(repository, "_transport", None)
762
if transport is not None:
764
transport.mkdir('git')
765
except bzrlib.errors.FileExists:
767
return cls(transport.clone('git'))
768
from bzrlib.transport import get_transport
769
return cls(get_transport(get_cache_dir()))
772
if self._transport is not None:
773
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
775
return "%s()" % (self.__class__.__name__)
778
assert self._builder is None
779
self.start_write_group()
780
for _, key, value in self._index.iter_all_entries():
781
self._builder.add_node(key, value)
783
for name in self._transport.list_dir('.'):
784
if name.endswith('.rix'):
785
to_remove.append(name)
786
self.commit_write_group()
787
del self._index.indices[1:]
788
for name in to_remove:
789
self._transport.rename(name, name + '.old')
791
def start_write_group(self):
792
assert self._builder is None
793
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
794
self._name = osutils.sha()
796
def commit_write_group(self):
797
assert self._builder is not None
798
stream = self._builder.finish()
799
name = self._name.hexdigest() + ".rix"
800
size = self._transport.put_file(name, stream)
801
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
802
self._index.insert_index(0, index)
806
def abort_write_group(self):
807
assert self._builder is not None
811
def _add_node(self, key, value):
813
self._builder.add_node(key, value)
814
except bzrlib.errors.BadIndexDuplicateKey:
815
# Multiple bzr objects can have the same contents
820
def _get_entry(self, key):
821
entries = self._index.iter_entries([key])
823
return entries.next()[2]
824
except StopIteration:
825
if self._builder is None:
827
entries = self._builder.iter_entries([key])
829
return entries.next()[2]
830
except StopIteration:
833
def _iter_keys_prefix(self, prefix):
834
for entry in self._index.iter_entries_prefix([prefix]):
836
if self._builder is not None:
837
for entry in self._builder.iter_entries_prefix([prefix]):
840
def lookup_commit(self, revid):
841
return self._get_entry(("commit", revid, "X"))[:40]
843
def _add_git_sha(self, hexsha, type, type_data):
844
if hexsha is not None:
845
self._name.update(hexsha)
847
td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
850
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
852
# This object is not represented in Git - perhaps an empty
854
self._name.update(type + " ".join(type_data))
856
def lookup_blob_id(self, fileid, revision):
857
return self._get_entry(("blob", fileid, revision))
859
def lookup_git_sha(self, sha):
861
sha = sha_to_hex(sha)
862
data = self._get_entry(("git", sha, "X")).split(" ", 3)
863
if data[0] == "commit":
864
return ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
866
return (data[0], tuple(data[1:]))
869
"""List the revision ids known."""
870
for key in self._iter_keys_prefix(("commit", None, None)):
873
def missing_revisions(self, revids):
874
"""Return set of all the revisions that are not present."""
875
missing_revids = set(revids)
876
for _, key, value in self._index.iter_entries((
877
("commit", revid, "X") for revid in revids)):
878
missing_revids.remove(key[1])
879
return missing_revids
882
"""List the SHA1s."""
883
for key in self._iter_keys_prefix(("git", None, None)):
887
formats = registry.Registry()
888
formats.register(TdbGitCacheFormat().get_format_string(),
890
formats.register(SqliteGitCacheFormat().get_format_string(),
891
SqliteGitCacheFormat())
892
formats.register(IndexGitCacheFormat().get_format_string(),
893
IndexGitCacheFormat())
894
# In the future, this will become the default:
895
# formats.register('default', IndexGitCacheFormat())
899
formats.register('default', SqliteGitCacheFormat())
901
formats.register('default', TdbGitCacheFormat())
905
def migrate_ancient_formats(repo_transport):
906
# Prefer migrating git.db over git.tdb, since the latter may not
907
# be openable on some platforms.
908
if repo_transport.has("git.db"):
909
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
910
repo_transport.rename("git.db", "git/idmap.db")
911
elif repo_transport.has("git.tdb"):
912
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
913
repo_transport.rename("git.tdb", "git/idmap.tdb")
916
def remove_readonly_transport_decorator(transport):
917
if transport.is_readonly():
918
return transport._decorated
922
def from_repository(repository):
923
"""Open a cache file for a repository.
925
If the repository is remote and there is no transport available from it
926
this will use a local file in the users cache directory
927
(typically ~/.cache/bazaar/git/)
929
:param repository: A repository object
931
repo_transport = getattr(repository, "_transport", None)
932
if repo_transport is not None:
933
# Migrate older cache formats
934
repo_transport = remove_readonly_transport_decorator(repo_transport)
936
repo_transport.mkdir("git")
937
except bzrlib.errors.FileExists:
940
migrate_ancient_formats(repo_transport)
941
return BzrGitCacheFormat.from_repository(repository)