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)
516
class TdbGitCacheFormat(BzrGitCacheFormat):
517
"""Cache format for tdb-based caches."""
519
def get_format_string(self):
520
return 'bzr-git sha map version 3 using tdb\n'
522
def open(self, transport):
524
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
525
except bzrlib.errors.NotLocalUrl:
526
basepath = get_cache_dir()
527
assert isinstance(basepath, str)
529
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
532
"Unable to open existing bzr-git cache because 'tdb' is not "
536
class TdbGitShaMap(GitShaMap):
537
"""SHA Map that uses a TDB database.
541
"git <sha1>" -> "<type> <type-data1> <type-data2>"
542
"commit revid" -> "<sha1> <tree-id>"
543
"tree fileid revid" -> "<sha1>"
544
"blob fileid revid" -> "<sha1>"
548
TDB_HASH_SIZE = 50000
550
def __init__(self, path=None):
556
assert isinstance(path, str)
557
if not mapdbs().has_key(path):
558
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
559
os.O_RDWR|os.O_CREAT)
560
self.db = mapdbs()[path]
562
if int(self.db["version"]) not in (2, 3):
563
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
564
self.db["version"], self.TDB_MAP_VERSION)
568
self.db["version"] = str(self.TDB_MAP_VERSION)
570
def start_write_group(self):
571
"""Start writing changes."""
572
self.db.transaction_start()
574
def commit_write_group(self):
575
"""Commit any pending changes."""
576
self.db.transaction_commit()
578
def abort_write_group(self):
579
"""Abort any pending changes."""
580
self.db.transaction_cancel()
583
return "%s(%r)" % (self.__class__.__name__, self.path)
585
def lookup_commit(self, revid):
586
return sha_to_hex(self.db["commit\0" + revid][:20])
588
def lookup_blob_id(self, fileid, revision):
589
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
591
def lookup_git_sha(self, sha):
592
"""Lookup a Git sha in the database.
594
:param sha: Git object sha
595
:return: (type, type_data) with type_data:
596
commit: revid, tree sha
601
sha = hex_to_sha(sha)
602
data = self.db["git\0" + sha].split("\0")
603
if data[0] == "commit":
605
return (data[0], (data[1], data[2], {}))
607
return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
609
return (data[0], tuple(data[1:]))
611
def missing_revisions(self, revids):
614
if self.db.get("commit\0" + revid) is None:
619
"""List the revision ids known."""
620
for key in self.db.iterkeys():
621
if key.startswith("commit\0"):
625
"""List the SHA1s."""
626
for key in self.db.iterkeys():
627
if key.startswith("git\0"):
628
yield sha_to_hex(key[4:])
631
class VersionedFilesContentCache(ContentCache):
633
def __init__(self, vf):
637
self._vf.insert_record_stream(
638
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
639
obj.as_legacy_object_chunks())])
641
def __getitem__(self, sha):
642
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
643
entry = stream.next()
644
if entry.storage_kind == 'absent':
646
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
649
class GitObjectStoreContentCache(ContentCache):
651
def __init__(self, store):
654
def add_multi(self, objs):
655
self.store.add_objects(objs)
657
def add(self, obj, path):
658
self.store.add_object(obj)
660
def __getitem__(self, sha):
661
return self.store[sha]
664
class IndexCacheUpdater(CacheUpdater):
666
def __init__(self, cache, rev):
668
self.revid = rev.revision_id
669
self.parent_revids = rev.parent_ids
672
self._cache_objs = set()
674
def add_object(self, obj, ie, path):
675
if obj.type_name == "commit":
677
assert type(ie) is dict
678
self.cache.idmap._add_git_sha(obj.id, "commit",
679
(self.revid, obj.tree, ie))
680
self.cache.idmap._add_node(("commit", self.revid, "X"),
681
" ".join((obj.id, obj.tree)))
682
self._cache_objs.add((obj, path))
683
elif obj.type_name == "blob":
684
self.cache.idmap._add_git_sha(obj.id, "blob",
685
(ie.file_id, ie.revision))
686
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
687
if ie.kind == "symlink":
688
self._cache_objs.add((obj, path))
689
elif obj.type_name == "tree":
690
self.cache.idmap._add_git_sha(obj.id, "tree",
691
(ie.file_id, self.revid))
692
self._cache_objs.add((obj, path))
697
self.cache.content_cache.add_multi(self._cache_objs)
701
class IndexBzrGitCache(BzrGitCache):
703
def __init__(self, transport=None):
704
mapper = versionedfile.ConstantMapper("trees")
705
shamap = IndexGitShaMap(transport.clone('index'))
706
#trees_store = knit.make_file_factory(True, mapper)(transport)
707
#content_cache = VersionedFilesContentCache(trees_store)
708
from bzrlib.plugins.git.transportgit import TransportObjectStore
709
store = TransportObjectStore(transport.clone('objects'))
710
content_cache = GitObjectStoreContentCache(store)
711
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
715
class IndexGitCacheFormat(BzrGitCacheFormat):
717
def get_format_string(self):
718
return 'bzr-git sha map with git object cache version 1\n'
720
def initialize(self, transport):
721
super(IndexGitCacheFormat, self).initialize(transport)
722
transport.mkdir('index')
723
transport.mkdir('objects')
724
from bzrlib.plugins.git.transportgit import TransportObjectStore
725
TransportObjectStore.init(transport.clone('objects'))
727
def open(self, transport):
728
return IndexBzrGitCache(transport)
731
class IndexGitShaMap(GitShaMap):
732
"""SHA Map that uses the Bazaar APIs to store a cache.
734
BTree Index file with the following contents:
736
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
737
("commit", <revid>) -> "<sha1> <tree-id>"
738
("blob", <fileid>, <revid>) -> <sha1>
742
def __init__(self, transport=None):
743
if transport is None:
744
self._transport = None
745
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
746
self._builder = self._index
749
self._transport = transport
750
self._index = _mod_index.CombinedGraphIndex([])
751
for name in self._transport.list_dir("."):
752
if not name.endswith(".rix"):
754
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
755
self._transport.stat(name).st_size)
756
self._index.insert_index(0, x)
759
def from_repository(cls, repository):
760
transport = getattr(repository, "_transport", None)
761
if transport is not None:
763
transport.mkdir('git')
764
except bzrlib.errors.FileExists:
766
return cls(transport.clone('git'))
767
from bzrlib.transport import get_transport
768
return cls(get_transport(get_cache_dir()))
771
if self._transport is not None:
772
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
774
return "%s()" % (self.__class__.__name__)
777
assert self._builder is None
778
self.start_write_group()
779
for _, key, value in self._index.iter_all_entries():
780
self._builder.add_node(key, value)
782
for name in self._transport.list_dir('.'):
783
if name.endswith('.rix'):
784
to_remove.append(name)
785
self.commit_write_group()
786
del self._index.indices[1:]
787
for name in to_remove:
788
self._transport.rename(name, name + '.old')
790
def start_write_group(self):
791
assert self._builder is None
792
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
793
self._name = osutils.sha()
795
def commit_write_group(self):
796
assert self._builder is not None
797
stream = self._builder.finish()
798
name = self._name.hexdigest() + ".rix"
799
size = self._transport.put_file(name, stream)
800
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
801
self._index.insert_index(0, index)
805
def abort_write_group(self):
806
assert self._builder is not None
810
def _add_node(self, key, value):
812
self._builder.add_node(key, value)
813
except bzrlib.errors.BadIndexDuplicateKey:
814
# Multiple bzr objects can have the same contents
819
def _get_entry(self, key):
820
entries = self._index.iter_entries([key])
822
return entries.next()[2]
823
except StopIteration:
824
if self._builder is None:
826
entries = self._builder.iter_entries([key])
828
return entries.next()[2]
829
except StopIteration:
832
def _iter_keys_prefix(self, prefix):
833
for entry in self._index.iter_entries_prefix([prefix]):
835
if self._builder is not None:
836
for entry in self._builder.iter_entries_prefix([prefix]):
839
def lookup_commit(self, revid):
840
return self._get_entry(("commit", revid, "X"))[:40]
842
def _add_git_sha(self, hexsha, type, type_data):
843
if hexsha is not None:
844
self._name.update(hexsha)
846
td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
849
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
851
# This object is not represented in Git - perhaps an empty
853
self._name.update(type + " ".join(type_data))
855
def lookup_blob_id(self, fileid, revision):
856
return self._get_entry(("blob", fileid, revision))
858
def lookup_git_sha(self, sha):
860
sha = sha_to_hex(sha)
861
data = self._get_entry(("git", sha, "X")).split(" ", 3)
862
if data[0] == "commit":
863
return ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
865
return (data[0], tuple(data[1:]))
868
"""List the revision ids known."""
869
for key in self._iter_keys_prefix(("commit", None, None)):
872
def missing_revisions(self, revids):
873
"""Return set of all the revisions that are not present."""
874
missing_revids = set(revids)
875
for _, key, value in self._index.iter_entries((
876
("commit", revid, "X") for revid in revids)):
877
missing_revids.remove(key[1])
878
return missing_revids
881
"""List the SHA1s."""
882
for key in self._iter_keys_prefix(("git", None, None)):
886
formats = registry.Registry()
887
formats.register(TdbGitCacheFormat().get_format_string(),
889
formats.register(SqliteGitCacheFormat().get_format_string(),
890
SqliteGitCacheFormat())
891
formats.register(IndexGitCacheFormat().get_format_string(),
892
IndexGitCacheFormat())
893
# In the future, this will become the default:
894
# formats.register('default', IndexGitCacheFormat())
898
formats.register('default', SqliteGitCacheFormat())
900
formats.register('default', TdbGitCacheFormat())
904
def migrate_ancient_formats(repo_transport):
905
# Prefer migrating git.db over git.tdb, since the latter may not
906
# be openable on some platforms.
907
if repo_transport.has("git.db"):
908
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
909
repo_transport.rename("git.db", "git/idmap.db")
910
elif repo_transport.has("git.tdb"):
911
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
912
repo_transport.rename("git.tdb", "git/idmap.tdb")
915
def remove_readonly_transport_decorator(transport):
916
if transport.is_readonly():
917
return transport._decorated
921
def from_repository(repository):
922
"""Open a cache file for a repository.
924
If the repository is remote and there is no transport available from it
925
this will use a local file in the users cache directory
926
(typically ~/.cache/bazaar/git/)
928
:param repository: A repository object
930
repo_transport = getattr(repository, "_transport", None)
931
if repo_transport is not None:
932
# Migrate older cache formats
933
repo_transport = remove_readonly_transport_decorator(repo_transport)
935
repo_transport.mkdir("git")
936
except bzrlib.errors.FileExists:
939
migrate_ancient_formats(repo_transport)
940
return BzrGitCacheFormat.from_repository(repository)