75
94
return _mapdbs.cache
78
class InventorySHAMap(object):
79
"""Maps inventory file ids to Git SHAs."""
81
def lookup_blob(self, file_id, revision):
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
revision: revid, tree sha
106
raise NotImplementedError(self.lookup_git_sha)
108
def lookup_blob_id(self, file_id, revision):
82
109
"""Retrieve a Git blob SHA by file id.
84
111
:param file_id: File id of the file/symlink
85
112
:param revision: revision in which the file was last changed.
87
raise NotImplementedError(self.lookup_blob)
114
raise NotImplementedError(self.lookup_blob_id)
89
def lookup_tree(self, file_id):
116
def lookup_tree_id(self, file_id, revision):
90
117
"""Retrieve a Git tree SHA by file id.
92
raise NotImplementedError(self.lookup_tree)
95
class GitShaMap(object):
96
"""Git<->Bzr revision id mapping database."""
98
def _add_entry(self, sha, type, type_data):
99
"""Add a new entry to the database.
101
raise NotImplementedError(self._add_entry)
103
def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
105
"""Add multiple new entries to the database.
107
for (fileid, kind, hexsha, revision) in entries:
108
self._add_entry(hexsha, kind, (fileid, revision))
109
self._add_entry(commit_sha, "commit", (revid, root_tree_sha))
111
def get_inventory_sha_map(self, revid):
112
"""Return the inventory SHA map for a revision.
114
:param revid: Revision to fetch the map for
115
:return: A `InventorySHAMap`
117
raise NotImplementedError(self.get_inventory_sha_map)
119
def lookup_git_sha(self, sha):
120
"""Lookup a Git sha in the database.
121
:param sha: Git object sha
122
:return: (type, type_data) with type_data:
123
revision: revid, tree sha
125
raise NotImplementedError(self.lookup_git_sha)
119
raise NotImplementedError(self.lookup_tree_id)
127
121
def revids(self):
128
122
"""List the revision ids known."""
149
143
"""Abort any pending changes."""
146
class ContentCache(object):
147
"""Object that can cache Git objects."""
149
def add(self, object):
151
raise NotImplementedError(self.add)
153
def add_multi(self, objects):
154
"""Add multiple objects."""
158
def __getitem__(self, sha):
159
"""Retrieve an item, by SHA."""
160
raise NotImplementedError(self.__getitem__)
163
class BzrGitCacheFormat(object):
164
"""Bazaar-Git Cache Format."""
166
def get_format_string(self):
167
"""Return a single-line unique format string for this cache format."""
168
raise NotImplementedError(self.get_format_string)
170
def open(self, transport):
171
"""Open this format on a transport."""
172
raise NotImplementedError(self.open)
174
def initialize(self, transport):
175
"""Create a new instance of this cache format at transport."""
176
transport.put_bytes('format', self.get_format_string())
179
def from_transport(self, transport):
180
"""Open a cache file present on a transport, or initialize one.
182
:param transport: Transport to use
183
:return: A BzrGitCache instance
186
format_name = transport.get_bytes('format')
187
format = formats.get(format_name)
188
except bzrlib.errors.NoSuchFile:
189
format = formats.get('default')
190
format.initialize(transport)
191
return format.open(transport)
194
def from_repository(cls, repository):
195
"""Open a cache file for a repository.
197
This will use the repository's transport to store the cache file, or
198
use the users global cache directory if the repository has no
199
transport associated with it.
201
:param repository: Repository to open the cache for
202
:return: A `BzrGitCache`
204
repo_transport = getattr(repository, "_transport", None)
205
if repo_transport is not None:
206
# Even if we don't write to this repo, we should be able
207
# to update its cache.
208
repo_transport = remove_readonly_transport_decorator(repo_transport)
210
repo_transport.mkdir('git')
211
except bzrlib.errors.FileExists:
213
transport = repo_transport.clone('git')
215
transport = get_remote_cache_transport()
216
return cls.from_transport(transport)
219
class CacheUpdater(object):
220
"""Base class for objects that can update a bzr-git cache."""
222
def add_object(self, obj, ie, path):
223
raise NotImplementedError(self.add_object)
226
raise NotImplementedError(self.finish)
229
class BzrGitCache(object):
230
"""Caching backend."""
232
def __init__(self, idmap, content_cache, cache_updater_klass):
234
self.content_cache = content_cache
235
self._cache_updater_klass = cache_updater_klass
237
def get_updater(self, rev):
238
"""Update an object that implements the CacheUpdater interface for
241
return self._cache_updater_klass(self, rev)
244
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
247
class DictCacheUpdater(CacheUpdater):
248
"""Cache updater for dict-based caches."""
250
def __init__(self, cache, rev):
252
self.revid = rev.revision_id
253
self.parent_revids = rev.parent_ids
257
def add_object(self, obj, ie, path):
258
if obj.type_name == "commit":
261
type_data = (self.revid, self._commit.tree)
262
self.cache.idmap._by_revid[self.revid] = obj.id
263
elif obj.type_name in ("blob", "tree"):
265
if obj.type_name == "blob":
266
revision = ie.revision
268
revision = self.revid
269
type_data = (ie.file_id, revision)
270
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] =\
274
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
277
if self._commit is None:
278
raise AssertionError("No commit object added")
152
282
class DictGitShaMap(GitShaMap):
283
"""Git SHA map that uses a dictionary."""
154
285
def __init__(self):
155
286
self._by_sha = {}
156
287
self._by_fileid = {}
158
def _add_entry(self, sha, type, type_data):
159
self._by_sha[sha] = (type, type_data)
160
if type in ("blob", "tree"):
161
self._by_fileid.setdefault(type_data[1], {})[type_data[0]] = sha
163
def get_inventory_sha_map(self, revid):
165
class DictInventorySHAMap(InventorySHAMap):
167
def __init__(self, base, revid):
171
def lookup_blob(self, fileid, revision):
172
return self._base._by_fileid[revision][fileid]
174
def lookup_tree(self, fileid):
175
return self._base._by_fileid[self.revid][fileid]
177
return DictInventorySHAMap(self, revid)
290
def lookup_blob_id(self, fileid, revision):
291
return self._by_fileid[revision][fileid]
179
293
def lookup_git_sha(self, sha):
180
294
return self._by_sha[sha]
296
def lookup_tree_id(self, fileid, revision):
297
return self._by_fileid[revision][fileid]
299
def lookup_commit(self, revid):
300
return self._by_revid[revid]
182
302
def revids(self):
183
303
for key, (type, type_data) in self._by_sha.iteritems():
184
304
if type == "commit":
188
308
return self._by_sha.iterkeys()
311
class SqliteCacheUpdater(CacheUpdater):
313
def __init__(self, cache, rev):
315
self.db = self.cache.idmap.db
316
self.revid = rev.revision_id
321
def add_object(self, obj, ie, path):
322
if obj.type_name == "commit":
325
elif obj.type_name == "tree":
327
self._trees.append((obj.id, ie.file_id, self.revid))
328
elif obj.type_name == "blob":
330
self._blobs.append((obj.id, ie.file_id, ie.revision))
335
if self._commit is None:
336
raise AssertionError("No commit object added")
338
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
341
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
344
"replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
345
(self._commit.id, self.revid, self._commit.tree))
349
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
352
class SqliteGitCacheFormat(BzrGitCacheFormat):
354
def get_format_string(self):
355
return 'bzr-git sha map version 1 using sqlite\n'
357
def open(self, transport):
359
basepath = transport.local_abspath(".")
360
except bzrlib.errors.NotLocalUrl:
361
basepath = get_cache_dir()
362
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
191
365
class SqliteGitShaMap(GitShaMap):
366
"""Bazaar GIT Sha map that uses a sqlite database for storage."""
193
368
def __init__(self, path=None):
245
412
def commit_write_group(self):
248
def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
252
for (fileid, kind, hexsha, revision) in entries:
256
trees.append((hexsha, fileid, revid))
258
blobs.append((hexsha, fileid, revision))
262
self.db.executemany("replace into trees (sha1, fileid, revid) values (?, ?, ?)", trees)
264
self.db.executemany("replace into blobs (sha1, fileid, revid) values (?, ?, ?)", blobs)
265
self._add_entry(commit_sha, "commit", (revid, root_tree_sha))
267
def _add_entry(self, sha, type, type_data):
268
"""Add a new entry to the database.
270
assert isinstance(type_data, tuple)
273
assert isinstance(sha, str), "type was %r" % sha
275
self.db.execute("replace into commits (sha1, revid, tree_sha) values (?, ?, ?)", (sha, type_data[0], type_data[1]))
276
elif type in ("blob", "tree"):
277
self.db.execute("replace into %ss (sha1, fileid, revid) values (?, ?, ?)" % type, (sha, type_data[0], type_data[1]))
279
raise AssertionError("Unknown type %s" % type)
281
def get_inventory_sha_map(self, revid):
282
class SqliteInventorySHAMap(InventorySHAMap):
284
def __init__(self, db, revid):
288
def lookup_blob(self, fileid, revision):
289
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
292
raise KeyError(fileid)
294
def lookup_tree(self, fileid):
295
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, self.revid)).fetchone()
298
raise KeyError(fileid)
300
return SqliteInventorySHAMap(self.db, revid)
415
def lookup_blob_id(self, fileid, revision):
416
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
419
raise KeyError(fileid)
421
def lookup_tree_id(self, fileid, revision):
422
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
425
raise KeyError(fileid)
302
427
def lookup_git_sha(self, sha):
303
428
"""Lookup a Git sha in the database.
451
597
yield sha_to_hex(key[4:])
600
class VersionedFilesContentCache(ContentCache):
602
def __init__(self, vf):
606
self._vf.insert_record_stream(
607
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
608
obj.as_legacy_object_chunks())])
610
def __getitem__(self, sha):
611
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
612
entry = stream.next()
613
if entry.storage_kind == 'absent':
615
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
618
class GitObjectStoreContentCache(ContentCache):
620
def __init__(self, store):
623
def add_multi(self, objs):
624
self.store.add_objects(objs)
626
def add(self, obj, path):
627
self.store.add_object(obj)
629
def __getitem__(self, sha):
630
return self.store[sha]
633
class IndexCacheUpdater(CacheUpdater):
635
def __init__(self, cache, rev):
637
self.revid = rev.revision_id
638
self.parent_revids = rev.parent_ids
641
self._cache_objs = set()
643
def add_object(self, obj, ie, path):
644
if obj.type_name == "commit":
647
self.cache.idmap._add_git_sha(obj.id, "commit",
648
(self.revid, obj.tree))
649
self.cache.idmap._add_node(("commit", self.revid, "X"),
650
" ".join((obj.id, obj.tree)))
651
self._cache_objs.add((obj, path))
652
elif obj.type_name == "blob":
653
self.cache.idmap._add_git_sha(obj.id, "blob",
654
(ie.file_id, ie.revision))
655
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
656
if ie.kind == "symlink":
657
self._cache_objs.add((obj, path))
658
elif obj.type_name == "tree":
659
self.cache.idmap._add_git_sha(obj.id, "tree",
660
(ie.file_id, self.revid))
661
self._cache_objs.add((obj, path))
666
self.cache.content_cache.add_multi(self._cache_objs)
670
class IndexBzrGitCache(BzrGitCache):
672
def __init__(self, transport=None):
673
mapper = versionedfile.ConstantMapper("trees")
674
shamap = IndexGitShaMap(transport.clone('index'))
675
#trees_store = knit.make_file_factory(True, mapper)(transport)
676
#content_cache = VersionedFilesContentCache(trees_store)
677
from bzrlib.plugins.git.transportgit import TransportObjectStore
678
store = TransportObjectStore(transport.clone('objects'))
679
content_cache = GitObjectStoreContentCache(store)
680
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
684
class IndexGitCacheFormat(BzrGitCacheFormat):
686
def get_format_string(self):
687
return 'bzr-git sha map with git object cache version 1\n'
689
def initialize(self, transport):
690
super(IndexGitCacheFormat, self).initialize(transport)
691
transport.mkdir('index')
692
transport.mkdir('objects')
693
from bzrlib.plugins.git.transportgit import TransportObjectStore
694
TransportObjectStore.init(transport.clone('objects'))
696
def open(self, transport):
697
return IndexBzrGitCache(transport)
700
class IndexGitShaMap(GitShaMap):
701
"""SHA Map that uses the Bazaar APIs to store a cache.
703
BTree Index file with the following contents:
705
("git", <sha1>) -> "<type> <type-data1> <type-data2>"
706
("commit", <revid>) -> "<sha1> <tree-id>"
707
("blob", <fileid>, <revid>) -> <sha1>
711
def __init__(self, transport=None):
712
if transport is None:
713
self._transport = None
714
self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
715
self._builder = self._index
718
self._transport = transport
719
self._index = _mod_index.CombinedGraphIndex([])
720
for name in self._transport.list_dir("."):
721
if not name.endswith(".rix"):
723
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
724
self._transport.stat(name).st_size)
725
self._index.insert_index(0, x)
728
def from_repository(cls, repository):
729
transport = getattr(repository, "_transport", None)
730
if transport is not None:
732
transport.mkdir('git')
733
except bzrlib.errors.FileExists:
735
return cls(transport.clone('git'))
736
from bzrlib.transport import get_transport
737
return cls(get_transport(get_cache_dir()))
740
if self._transport is not None:
741
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
743
return "%s()" % (self.__class__.__name__)
746
assert self._builder is None
747
self.start_write_group()
748
for _, key, value in self._index.iter_all_entries():
749
self._builder.add_node(key, value)
751
for name in self._transport.list_dir('.'):
752
if name.endswith('.rix'):
753
to_remove.append(name)
754
self.commit_write_group()
755
del self._index.indices[1:]
756
for name in to_remove:
757
self._transport.rename(name, name + '.old')
759
def start_write_group(self):
760
assert self._builder is None
761
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
762
self._name = osutils.sha()
764
def commit_write_group(self):
765
assert self._builder is not None
766
stream = self._builder.finish()
767
name = self._name.hexdigest() + ".rix"
768
size = self._transport.put_file(name, stream)
769
index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
770
self._index.insert_index(0, index)
774
def abort_write_group(self):
775
assert self._builder is not None
779
def _add_node(self, key, value):
781
self._builder.add_node(key, value)
782
except bzrlib.errors.BadIndexDuplicateKey:
783
# Multiple bzr objects can have the same contents
788
def _get_entry(self, key):
789
entries = self._index.iter_entries([key])
791
return entries.next()[2]
792
except StopIteration:
793
if self._builder is None:
795
entries = self._builder.iter_entries([key])
797
return entries.next()[2]
798
except StopIteration:
801
def _iter_keys_prefix(self, prefix):
802
for entry in self._index.iter_entries_prefix([prefix]):
804
if self._builder is not None:
805
for entry in self._builder.iter_entries_prefix([prefix]):
808
def lookup_commit(self, revid):
809
return self._get_entry(("commit", revid, "X"))[:40]
811
def _add_git_sha(self, hexsha, type, type_data):
812
if hexsha is not None:
813
self._name.update(hexsha)
814
self._add_node(("git", hexsha, "X"),
815
" ".join((type, type_data[0], type_data[1])))
817
# This object is not represented in Git - perhaps an empty
819
self._name.update(type + " ".join(type_data))
821
def lookup_blob_id(self, fileid, revision):
822
return self._get_entry(("blob", fileid, revision))
824
def lookup_git_sha(self, sha):
826
sha = sha_to_hex(sha)
827
data = self._get_entry(("git", sha, "X")).split(" ", 2)
828
return (data[0], (data[1], data[2]))
831
"""List the revision ids known."""
832
for key in self._iter_keys_prefix(("commit", None, None)):
835
def missing_revisions(self, revids):
836
"""Return set of all the revisions that are not present."""
837
missing_revids = set(revids)
838
for _, key, value in self._index.iter_entries((
839
("commit", revid, "X") for revid in revids)):
840
missing_revids.remove(key[1])
841
return missing_revids
844
"""List the SHA1s."""
845
for key in self._iter_keys_prefix(("git", None, None)):
849
formats = registry.Registry()
850
formats.register(TdbGitCacheFormat().get_format_string(),
852
formats.register(SqliteGitCacheFormat().get_format_string(),
853
SqliteGitCacheFormat())
854
formats.register(IndexGitCacheFormat().get_format_string(),
855
IndexGitCacheFormat())
856
# In the future, this will become the default:
857
# formats.register('default', IndexGitCacheFormat())
861
formats.register('default', SqliteGitCacheFormat())
863
formats.register('default', TdbGitCacheFormat())
867
def migrate_ancient_formats(repo_transport):
868
# Prefer migrating git.db over git.tdb, since the latter may not
869
# be openable on some platforms.
870
if repo_transport.has("git.db"):
871
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
872
repo_transport.rename("git.db", "git/idmap.db")
873
elif repo_transport.has("git.tdb"):
874
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
875
repo_transport.rename("git.tdb", "git/idmap.tdb")
878
def remove_readonly_transport_decorator(transport):
879
if transport.is_readonly():
880
return transport._decorated
454
884
def from_repository(repository):
456
return TdbGitShaMap.from_repository(repository)
458
return SqliteGitShaMap.from_repository(repository)
885
"""Open a cache file for a repository.
887
If the repository is remote and there is no transport available from it
888
this will use a local file in the users cache directory
889
(typically ~/.cache/bazaar/git/)
891
:param repository: A repository object
893
repo_transport = getattr(repository, "_transport", None)
894
if repo_transport is not None:
895
# Migrate older cache formats
896
repo_transport = remove_readonly_transport_decorator(repo_transport)
898
repo_transport.mkdir("git")
899
except bzrlib.errors.FileExists:
902
migrate_ancient_formats(repo_transport)
903
return BzrGitCacheFormat.from_repository(repository)