1
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Map from Git sha's to Bazaar objects."""
19
from dulwich.objects import (
26
from dulwich.objects import (
32
btree_index as _mod_btree_index,
39
from bzrlib.transport import (
46
from xdg.BaseDirectory import xdg_cache_home
48
from bzrlib.config import config_dir
49
ret = os.path.join(config_dir(), "git")
51
ret = os.path.join(xdg_cache_home, "bazaar", "git")
52
if not os.path.isdir(ret):
57
def get_remote_cache_transport(repository):
58
"""Retrieve the transport to use when accessing (unwritable) remote
61
uuid = getattr(repository, "uuid", None)
63
path = get_cache_dir()
65
path = os.path.join(get_cache_dir(), uuid)
66
if not os.path.isdir(path):
68
return get_transport(path)
71
def check_pysqlite_version(sqlite3):
72
"""Check that sqlite library is compatible.
75
if (sqlite3.sqlite_version_info[0] < 3 or
76
(sqlite3.sqlite_version_info[0] == 3 and
77
sqlite3.sqlite_version_info[1] < 3)):
78
trace.warning('Needs at least sqlite 3.3.x')
79
raise bzrlib.errors.BzrError("incompatible sqlite library")
84
check_pysqlite_version(sqlite3)
85
except (ImportError, bzrlib.errors.BzrError), e:
86
from pysqlite2 import dbapi2 as sqlite3
87
check_pysqlite_version(sqlite3)
89
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
91
raise bzrlib.errors.BzrError("missing sqlite library")
94
_mapdbs = threading.local()
96
"""Get a cache for this thread's db connections."""
99
except AttributeError:
104
class GitShaMap(object):
105
"""Git<->Bzr revision id mapping database."""
107
def lookup_git_sha(self, sha):
108
"""Lookup a Git sha in the database.
109
:param sha: Git object sha
110
:return: list with (type, type_data) tuples with type_data:
111
commit: revid, tree_sha, verifiers
115
raise NotImplementedError(self.lookup_git_sha)
117
def lookup_blob_id(self, file_id, revision):
118
"""Retrieve a Git blob SHA by file id.
120
:param file_id: File id of the file/symlink
121
:param revision: revision in which the file was last changed.
123
raise NotImplementedError(self.lookup_blob_id)
125
def lookup_tree_id(self, file_id, revision):
126
"""Retrieve a Git tree SHA by file id.
128
raise NotImplementedError(self.lookup_tree_id)
130
def lookup_commit(self, revid):
131
"""Retrieve a Git commit SHA by Bazaar revision id.
133
raise NotImplementedError(self.lookup_commit)
136
"""List the revision ids known."""
137
raise NotImplementedError(self.revids)
139
def missing_revisions(self, revids):
140
"""Return set of all the revisions that are not present."""
141
present_revids = set(self.revids())
142
if not isinstance(revids, set):
144
return revids - present_revids
147
"""List the SHA1s."""
148
raise NotImplementedError(self.sha1s)
150
def start_write_group(self):
151
"""Start writing changes."""
153
def commit_write_group(self):
154
"""Commit any pending changes."""
156
def abort_write_group(self):
157
"""Abort any pending changes."""
160
class ContentCache(object):
161
"""Object that can cache Git objects."""
163
def add(self, object):
165
raise NotImplementedError(self.add)
167
def add_multi(self, objects):
168
"""Add multiple objects."""
172
def __getitem__(self, sha):
173
"""Retrieve an item, by SHA."""
174
raise NotImplementedError(self.__getitem__)
177
class BzrGitCacheFormat(object):
178
"""Bazaar-Git Cache Format."""
180
def get_format_string(self):
181
"""Return a single-line unique format string for this cache format."""
182
raise NotImplementedError(self.get_format_string)
184
def open(self, transport):
185
"""Open this format on a transport."""
186
raise NotImplementedError(self.open)
188
def initialize(self, transport):
189
"""Create a new instance of this cache format at transport."""
190
transport.put_bytes('format', self.get_format_string())
193
def from_transport(self, transport):
194
"""Open a cache file present on a transport, or initialize one.
196
:param transport: Transport to use
197
:return: A BzrGitCache instance
200
format_name = transport.get_bytes('format')
201
format = formats.get(format_name)
202
except bzrlib.errors.NoSuchFile:
203
format = formats.get('default')
204
format.initialize(transport)
205
return format.open(transport)
208
def from_repository(cls, repository):
209
"""Open a cache file for a repository.
211
This will use the repository's transport to store the cache file, or
212
use the users global cache directory if the repository has no
213
transport associated with it.
215
:param repository: Repository to open the cache for
216
:return: A `BzrGitCache`
218
from bzrlib.transport.local import LocalTransport
219
repo_transport = getattr(repository, "_transport", None)
220
if repo_transport is not None and isinstance(repo_transport, LocalTransport):
221
# Even if we don't write to this repo, we should be able
222
# to update its cache.
223
repo_transport = remove_readonly_transport_decorator(repo_transport)
225
repo_transport.mkdir('git')
226
except bzrlib.errors.FileExists:
228
transport = repo_transport.clone('git')
230
transport = get_remote_cache_transport(repository)
231
return cls.from_transport(transport)
234
class CacheUpdater(object):
235
"""Base class for objects that can update a bzr-git cache."""
237
def add_object(self, obj, ie, path):
240
:param obj: Object type ("commit", "blob" or "tree")
241
:param ie: Inventory entry (for blob/tree) or testament_sha in case
243
:param path: Path of the object (optional)
245
raise NotImplementedError(self.add_object)
248
raise NotImplementedError(self.finish)
251
class BzrGitCache(object):
252
"""Caching backend."""
254
def __init__(self, idmap, content_cache, cache_updater_klass):
256
self.content_cache = content_cache
257
self._cache_updater_klass = cache_updater_klass
259
def get_updater(self, rev):
260
"""Update an object that implements the CacheUpdater interface for
263
return self._cache_updater_klass(self, rev)
266
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
269
class DictCacheUpdater(CacheUpdater):
270
"""Cache updater for dict-based caches."""
272
def __init__(self, cache, rev):
274
self.revid = rev.revision_id
275
self.parent_revids = rev.parent_ids
279
def add_object(self, obj, ie, path):
280
if obj.type_name == "commit":
282
assert type(ie) is dict
284
type_data = (self.revid, self._commit.tree, ie)
285
self.cache.idmap._by_revid[self.revid] = obj.id
286
elif obj.type_name in ("blob", "tree"):
288
if obj.type_name == "blob":
289
revision = ie.revision
291
revision = self.revid
292
key = type_data = (ie.file_id, revision)
293
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
296
entry = (obj.type_name, type_data)
297
self.cache.idmap._by_sha.setdefault(obj.id, {})[key] = entry
300
if self._commit is None:
301
raise AssertionError("No commit object added")
305
class DictGitShaMap(GitShaMap):
306
"""Git SHA map that uses a dictionary."""
313
def lookup_blob_id(self, fileid, revision):
314
return self._by_fileid[revision][fileid]
316
def lookup_git_sha(self, sha):
317
for entry in self._by_sha[sha].itervalues():
320
def lookup_tree_id(self, fileid, revision):
321
return self._by_fileid[revision][fileid]
323
def lookup_commit(self, revid):
324
return self._by_revid[revid]
327
for key, entries in self._by_sha.iteritems():
328
for (type, type_data) in entries.values():
333
return self._by_sha.iterkeys()
336
class SqliteCacheUpdater(CacheUpdater):
338
def __init__(self, cache, rev):
340
self.db = self.cache.idmap.db
341
self.revid = rev.revision_id
346
def add_object(self, obj, ie, path):
347
if obj.type_name == "commit":
349
self._testament3_sha1 = ie.get("testament3-sha1")
350
assert type(ie) is dict
351
elif obj.type_name == "tree":
353
self._trees.append((obj.id, ie.file_id, self.revid))
354
elif obj.type_name == "blob":
356
self._blobs.append((obj.id, ie.file_id, ie.revision))
361
if self._commit is None:
362
raise AssertionError("No commit object added")
364
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
367
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
370
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
371
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
375
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
378
class SqliteGitCacheFormat(BzrGitCacheFormat):
380
def get_format_string(self):
381
return 'bzr-git sha map version 1 using sqlite\n'
383
def open(self, transport):
385
basepath = transport.local_abspath(".")
386
except bzrlib.errors.NotLocalUrl:
387
basepath = get_cache_dir()
388
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
391
class SqliteGitShaMap(GitShaMap):
392
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
394
def __init__(self, path=None):
397
self.db = sqlite3.connect(":memory:")
399
if not mapdbs().has_key(path):
400
mapdbs()[path] = sqlite3.connect(path)
401
self.db = mapdbs()[path]
402
self.db.text_factory = str
403
self.db.executescript("""
404
create table if not exists commits(
405
sha1 text not null check(length(sha1) == 40),
407
tree_sha text not null check(length(tree_sha) == 40)
409
create index if not exists commit_sha1 on commits(sha1);
410
create unique index if not exists commit_revid on commits(revid);
411
create table if not exists blobs(
412
sha1 text not null check(length(sha1) == 40),
413
fileid text not null,
416
create index if not exists blobs_sha1 on blobs(sha1);
417
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
418
create table if not exists trees(
419
sha1 text unique not null check(length(sha1) == 40),
420
fileid text not null,
423
create unique index if not exists trees_sha1 on trees(sha1);
424
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
427
self.db.executescript(
428
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
429
except sqlite3.OperationalError:
430
pass # Column already exists.
433
return "%s(%r)" % (self.__class__.__name__, self.path)
435
def lookup_commit(self, revid):
436
cursor = self.db.execute("select sha1 from commits where revid = ?",
438
row = cursor.fetchone()
443
def commit_write_group(self):
446
def lookup_blob_id(self, fileid, revision):
447
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
450
raise KeyError(fileid)
452
def lookup_tree_id(self, fileid, revision):
453
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
456
raise KeyError(fileid)
458
def lookup_git_sha(self, sha):
459
"""Lookup a Git sha in the database.
461
:param sha: Git object sha
462
:return: (type, type_data) with type_data:
463
commit: revid, tree sha, verifiers
468
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
469
for row in cursor.fetchall():
471
if row[2] is not None:
472
verifiers = {"testament3-sha1": row[2]}
475
yield ("commit", (row[0], row[1], verifiers))
476
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
477
for row in cursor.fetchall():
480
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
481
for row in cursor.fetchall():
488
"""List the revision ids known."""
489
return (row for (row,) in self.db.execute("select revid from commits"))
492
"""List the SHA1s."""
493
for table in ("blobs", "commits", "trees"):
494
for (sha,) in self.db.execute("select sha1 from %s" % table):
498
class TdbCacheUpdater(CacheUpdater):
499
"""Cache updater for tdb-based caches."""
501
def __init__(self, cache, rev):
503
self.db = cache.idmap.db
504
self.revid = rev.revision_id
505
self.parent_revids = rev.parent_ids
509
def add_object(self, obj, ie, path):
510
sha = obj.sha().digest()
511
if obj.type_name == "commit":
512
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
513
assert type(ie) is dict, "was %r" % ie
514
type_data = (self.revid, obj.tree)
516
type_data += (ie["testament3-sha1"],)
520
elif obj.type_name == "blob":
523
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
524
type_data = (ie.file_id, ie.revision)
525
elif obj.type_name == "tree":
528
type_data = (ie.file_id, self.revid)
531
entry = "\0".join((obj.type_name, ) + type_data) + "\n"
534
oldval = self.db[key]
538
if oldval[-1] != "\n":
539
self.db[key] = "".join([oldval, "\n", entry])
541
self.db[key] = "".join([oldval, entry])
544
if self._commit is None:
545
raise AssertionError("No commit object added")
549
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
552
class TdbGitCacheFormat(BzrGitCacheFormat):
553
"""Cache format for tdb-based caches."""
555
def get_format_string(self):
556
return 'bzr-git sha map version 3 using tdb\n'
558
def open(self, transport):
560
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
561
except bzrlib.errors.NotLocalUrl:
562
basepath = get_cache_dir()
563
assert isinstance(basepath, str)
565
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
568
"Unable to open existing bzr-git cache because 'tdb' is not "
572
class TdbGitShaMap(GitShaMap):
573
"""SHA Map that uses a TDB database.
577
"git <sha1>" -> "<type> <type-data1> <type-data2>"
578
"commit revid" -> "<sha1> <tree-id>"
579
"tree fileid revid" -> "<sha1>"
580
"blob fileid revid" -> "<sha1>"
584
TDB_HASH_SIZE = 50000
586
def __init__(self, path=None):
592
assert isinstance(path, str)
593
if not mapdbs().has_key(path):
594
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
595
os.O_RDWR|os.O_CREAT)
596
self.db = mapdbs()[path]
598
if int(self.db["version"]) not in (2, 3):
599
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
600
self.db["version"], self.TDB_MAP_VERSION)
604
self.db["version"] = str(self.TDB_MAP_VERSION)
606
def start_write_group(self):
607
"""Start writing changes."""
608
self.db.transaction_start()
610
def commit_write_group(self):
611
"""Commit any pending changes."""
612
self.db.transaction_commit()
614
def abort_write_group(self):
615
"""Abort any pending changes."""
616
self.db.transaction_cancel()
619
return "%s(%r)" % (self.__class__.__name__, self.path)
621
def lookup_commit(self, revid):
623
return sha_to_hex(self.db["commit\0" + revid][:20])
625
raise KeyError("No cache entry for %r" % revid)
627
def lookup_blob_id(self, fileid, revision):
628
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
630
def lookup_git_sha(self, sha):
631
"""Lookup a Git sha in the database.
633
:param sha: Git object sha
634
:return: (type, type_data) with type_data:
635
commit: revid, tree sha
640
sha = hex_to_sha(sha)
641
value = self.db["git\0" + sha]
642
for data in value.splitlines():
643
data = data.split("\0")
644
if data[0] == "commit":
646
yield (data[0], (data[1], data[2], {}))
648
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
649
elif data[0] in ("tree", "blob"):
650
yield (data[0], tuple(data[1:]))
652
raise AssertionError("unknown type %r" % data[0])
654
def missing_revisions(self, revids):
657
if self.db.get("commit\0" + revid) is None:
662
"""List the revision ids known."""
663
for key in self.db.iterkeys():
664
if key.startswith("commit\0"):
668
"""List the SHA1s."""
669
for key in self.db.iterkeys():
670
if key.startswith("git\0"):
671
yield sha_to_hex(key[4:])
674
class VersionedFilesContentCache(ContentCache):
676
def __init__(self, vf):
680
self._vf.insert_record_stream(
681
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
682
obj.as_legacy_object_chunks())])
684
def __getitem__(self, sha):
685
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
686
entry = stream.next()
687
if entry.storage_kind == 'absent':
689
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
692
class GitObjectStoreContentCache(ContentCache):
694
def __init__(self, store):
697
def add_multi(self, objs):
698
self.store.add_objects(objs)
700
def add(self, obj, path):
701
self.store.add_object(obj)
703
def __getitem__(self, sha):
704
return self.store[sha]
707
class IndexCacheUpdater(CacheUpdater):
709
def __init__(self, cache, rev):
711
self.revid = rev.revision_id
712
self.parent_revids = rev.parent_ids
715
self._cache_objs = set()
717
def add_object(self, obj, ie, path):
718
if obj.type_name == "commit":
720
assert type(ie) is dict
721
self.cache.idmap._add_git_sha(obj.id, "commit",
722
(self.revid, obj.tree, ie))
723
self.cache.idmap._add_node(("commit", self.revid, "X"),
724
" ".join((obj.id, obj.tree)))
725
self._cache_objs.add((obj, path))
726
elif obj.type_name == "blob":
727
self.cache.idmap._add_git_sha(obj.id, "blob",
728
(ie.file_id, ie.revision))
729
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
730
if ie.kind == "symlink":
731
self._cache_objs.add((obj, path))
732
elif obj.type_name == "tree":
733
self.cache.idmap._add_git_sha(obj.id, "tree",
734
(ie.file_id, self.revid))
735
self._cache_objs.add((obj, path))
740
self.cache.content_cache.add_multi(self._cache_objs)
744
class IndexBzrGitCache(BzrGitCache):
746
def __init__(self, transport=None):
747
mapper = versionedfile.ConstantMapper("trees")
748
shamap = IndexGitShaMap(transport.clone('index'))
749
#trees_store = knit.make_file_factory(True, mapper)(transport)
750
#content_cache = VersionedFilesContentCache(trees_store)
751
from bzrlib.plugins.git.transportgit import TransportObjectStore
752
store = TransportObjectStore(transport.clone('objects'))
753
content_cache = GitObjectStoreContentCache(store)
754
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
758
class IndexGitCacheFormat(BzrGitCacheFormat):
760
def get_format_string(self):
761
return 'bzr-git sha map with git object cache version 1\n'
763
def initialize(self, transport):
764
super(IndexGitCacheFormat, self).initialize(transport)
765
transport.mkdir('index')
766
transport.mkdir('objects')
767
from bzrlib.plugins.git.transportgit import TransportObjectStore
768
TransportObjectStore.init(transport.clone('objects'))
770
def open(self, transport):
771
return IndexBzrGitCache(transport)
774
class IndexGitShaMap(GitShaMap):
775
"""SHA Map that uses the Bazaar APIs to store a cache.
777
BTree Index file with the following contents:
779
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
780
("commit", <revid>) -> "<sha1> <tree-id>"
781
("blob", <fileid>, <revid>) -> <sha1>
785
def __init__(self, transport=None):
786
if transport is None:
787
self._transport = None
788
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
789
self._builder = self._index
792
self._transport = transport
793
self._index = _mod_index.CombinedGraphIndex([])
794
for name in self._transport.list_dir("."):
795
if not name.endswith(".rix"):
797
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
798
self._transport.stat(name).st_size)
799
self._index.insert_index(0, x)
802
def from_repository(cls, repository):
803
transport = getattr(repository, "_transport", None)
804
if transport is not None:
806
transport.mkdir('git')
807
except bzrlib.errors.FileExists:
809
return cls(transport.clone('git'))
810
from bzrlib.transport import get_transport
811
return cls(get_transport(get_cache_dir()))
814
if self._transport is not None:
815
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
817
return "%s()" % (self.__class__.__name__)
820
assert self._builder is None
821
self.start_write_group()
822
for _, key, value in self._index.iter_all_entries():
823
self._builder.add_node(key, value)
825
for name in self._transport.list_dir('.'):
826
if name.endswith('.rix'):
827
to_remove.append(name)
828
self.commit_write_group()
829
del self._index.indices[1:]
830
for name in to_remove:
831
self._transport.rename(name, name + '.old')
833
def start_write_group(self):
834
assert self._builder is None
835
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
836
self._name = osutils.sha()
838
def commit_write_group(self):
839
assert self._builder is not None
840
stream = self._builder.finish()
841
name = self._name.hexdigest() + ".rix"
842
size = self._transport.put_file(name, stream)
843
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
844
self._index.insert_index(0, index)
848
def abort_write_group(self):
849
assert self._builder is not None
853
def _add_node(self, key, value):
855
self._builder.add_node(key, value)
856
except bzrlib.errors.BadIndexDuplicateKey:
857
# Multiple bzr objects can have the same contents
862
def _get_entry(self, key):
863
entries = self._index.iter_entries([key])
865
return entries.next()[2]
866
except StopIteration:
867
if self._builder is None:
869
entries = self._builder.iter_entries([key])
871
return entries.next()[2]
872
except StopIteration:
875
def _iter_entries_prefix(self, prefix):
876
for entry in self._index.iter_entries_prefix([prefix]):
877
yield (entry[1], entry[2])
878
if self._builder is not None:
879
for entry in self._builder.iter_entries_prefix([prefix]):
880
yield (entry[1], entry[2])
882
def lookup_commit(self, revid):
883
return self._get_entry(("commit", revid, "X"))[:40]
885
def _add_git_sha(self, hexsha, type, type_data):
886
if hexsha is not None:
887
self._name.update(hexsha)
889
td = (type_data[0], type_data[1])
891
td += (type_data[2]["testament3-sha1"],)
896
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
898
# This object is not represented in Git - perhaps an empty
900
self._name.update(type + " ".join(type_data))
902
def lookup_blob_id(self, fileid, revision):
903
return self._get_entry(("blob", fileid, revision))
905
def lookup_git_sha(self, sha):
907
sha = sha_to_hex(sha)
909
for key, value in self._iter_entries_prefix(("git", sha, None)):
911
data = value.split(" ", 3)
912
if data[0] == "commit":
914
verifiers = {"testament3-sha1": data[3]}
917
yield ("commit", (data[1], data[2], verifiers))
919
yield (data[0], tuple(data[1:]))
924
"""List the revision ids known."""
925
for key, value in self._iter_entries_prefix(("commit", None, None)):
928
def missing_revisions(self, revids):
929
"""Return set of all the revisions that are not present."""
930
missing_revids = set(revids)
931
for _, key, value in self._index.iter_entries((
932
("commit", revid, "X") for revid in revids)):
933
missing_revids.remove(key[1])
934
return missing_revids
937
"""List the SHA1s."""
938
for key, value in self._iter_entries_prefix(("git", None, None)):
942
formats = registry.Registry()
943
formats.register(TdbGitCacheFormat().get_format_string(),
945
formats.register(SqliteGitCacheFormat().get_format_string(),
946
SqliteGitCacheFormat())
947
formats.register(IndexGitCacheFormat().get_format_string(),
948
IndexGitCacheFormat())
949
# In the future, this will become the default:
950
# formats.register('default', IndexGitCacheFormat())
954
formats.register('default', SqliteGitCacheFormat())
956
formats.register('default', TdbGitCacheFormat())
960
def migrate_ancient_formats(repo_transport):
961
# Migrate older cache formats
962
repo_transport = remove_readonly_transport_decorator(repo_transport)
963
has_sqlite = repo_transport.has("git.db")
964
has_tdb = repo_transport.has("git.tdb")
965
if not has_sqlite or has_tdb:
968
repo_transport.mkdir("git")
969
except bzrlib.errors.FileExists:
971
# Prefer migrating git.db over git.tdb, since the latter may not
972
# be openable on some platforms.
974
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
975
repo_transport.rename("git.db", "git/idmap.db")
977
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
978
repo_transport.rename("git.tdb", "git/idmap.tdb")
981
def remove_readonly_transport_decorator(transport):
982
if transport.is_readonly():
983
return transport._decorated
987
def from_repository(repository):
988
"""Open a cache file for a repository.
990
If the repository is remote and there is no transport available from it
991
this will use a local file in the users cache directory
992
(typically ~/.cache/bazaar/git/)
994
:param repository: A repository object
996
repo_transport = getattr(repository, "_transport", None)
997
if repo_transport is not None:
998
migrate_ancient_formats(repo_transport)
999
return BzrGitCacheFormat.from_repository(repository)