75
83
return _mapdbs.cache
78
class InventorySHAMap(object):
79
"""Maps inventory file ids to Git SHAs."""
81
def lookup_blob(self, file_id, revision):
86
class GitShaMap(object):
87
"""Git<->Bzr revision id mapping database."""
89
def lookup_git_sha(self, sha):
90
"""Lookup a Git sha in the database.
91
:param sha: Git object sha
92
:return: (type, type_data) with type_data:
93
revision: revid, tree sha
95
raise NotImplementedError(self.lookup_git_sha)
97
def lookup_blob_id(self, file_id, revision):
82
98
"""Retrieve a Git blob SHA by file id.
84
100
:param file_id: File id of the file/symlink
85
101
:param revision: revision in which the file was last changed.
87
raise NotImplementedError(self.lookup_blob)
103
raise NotImplementedError(self.lookup_blob_id)
89
def lookup_tree(self, file_id):
105
def lookup_tree_id(self, file_id, revision):
90
106
"""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)
108
raise NotImplementedError(self.lookup_tree_id)
127
110
def revids(self):
128
111
"""List the revision ids known."""
149
132
"""Abort any pending changes."""
135
class ContentCache(object):
136
"""Object that can cache Git objects."""
138
def __getitem__(self, sha):
139
"""Retrieve an item, by SHA."""
140
raise NotImplementedError(self.__getitem__)
143
class BzrGitCacheFormat(object):
145
def get_format_string(self):
146
"""Return a single-line unique format string for this cache format."""
147
raise NotImplementedError(self.get_format_string)
149
def open(self, transport):
150
"""Open this format on a transport."""
151
raise NotImplementedError(self.open)
153
def initialize(self, transport):
154
transport.put_bytes('format', self.get_format_string())
157
def from_transport(self, transport):
158
"""Open a cache file present on a transport, or initialize one.
160
:param transport: Transport to use
161
:return: A BzrGitCache instance
164
format_name = transport.get_bytes('format')
165
format = formats.get(format_name)
166
except bzrlib.errors.NoSuchFile:
167
format = formats.get('default')
168
format.initialize(transport)
169
return format.open(transport)
172
def from_repository(cls, repository):
173
"""Open a cache file for a repository.
175
This will use the repository's transport to store the cache file, or
176
use the users global cache directory if the repository has no
177
transport associated with it.
179
:param repository: Repository to open the cache for
180
:return: A `BzrGitCache`
182
repo_transport = getattr(repository, "_transport", None)
183
if repo_transport is not None:
184
# Even if we don't write to this repo, we should be able
185
# to update its cache.
186
repo_transport = remove_readonly_transport_decorator(repo_transport)
188
repo_transport.mkdir('git')
189
except bzrlib.errors.FileExists:
191
transport = repo_transport.clone('git')
193
transport = get_remote_cache_transport()
194
return cls.from_transport(transport)
197
class CacheUpdater(object):
199
def add_object(self, obj, ie):
200
raise NotImplementedError(self.add_object)
203
raise NotImplementedError(self.finish)
206
class BzrGitCache(object):
207
"""Caching backend."""
209
def __init__(self, idmap, content_cache, cache_updater_klass):
211
self.content_cache = content_cache
212
self._cache_updater_klass = cache_updater_klass
214
def get_updater(self, rev):
215
return self._cache_updater_klass(self, rev)
218
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
221
class DictCacheUpdater(CacheUpdater):
223
def __init__(self, cache, rev):
225
self.revid = rev.revision_id
226
self.parent_revids = rev.parent_ids
230
def add_object(self, obj, ie):
231
if obj.type_name == "commit":
234
type_data = (self.revid, self._commit.tree)
235
self.cache.idmap._by_revid[self.revid] = obj.id
236
elif obj.type_name in ("blob", "tree"):
238
if obj.type_name == "blob":
239
revision = ie.revision
241
revision = self.revid
242
type_data = (ie.file_id, revision)
243
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] =\
247
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
250
if self._commit is None:
251
raise AssertionError("No commit object added")
152
255
class DictGitShaMap(GitShaMap):
154
257
def __init__(self):
155
258
self._by_sha = {}
156
259
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)
262
def lookup_blob_id(self, fileid, revision):
263
return self._by_fileid[revision][fileid]
179
265
def lookup_git_sha(self, sha):
180
266
return self._by_sha[sha]
268
def lookup_tree_id(self, fileid, revision):
269
return self._by_fileid[revision][fileid]
271
def lookup_commit(self, revid):
272
return self._by_revid[revid]
182
274
def revids(self):
183
275
for key, (type, type_data) in self._by_sha.iteritems():
184
276
if type == "commit":
188
280
return self._by_sha.iterkeys()
283
class SqliteCacheUpdater(CacheUpdater):
285
def __init__(self, cache, rev):
287
self.db = self.cache.idmap.db
288
self.revid = rev.revision_id
293
def add_object(self, obj, ie):
294
if obj.type_name == "commit":
297
elif obj.type_name == "tree":
299
self._trees.append((obj.id, ie.file_id, self.revid))
300
elif obj.type_name == "blob":
302
self._blobs.append((obj.id, ie.file_id, ie.revision))
307
if self._commit is None:
308
raise AssertionError("No commit object added")
310
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
313
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
316
"replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
317
(self._commit.id, self.revid, self._commit.tree))
321
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
324
class SqliteGitCacheFormat(BzrGitCacheFormat):
326
def get_format_string(self):
327
return 'bzr-git sha map version 1 using sqlite\n'
329
def open(self, transport):
331
basepath = transport.local_abspath(".")
332
except bzrlib.errors.NotLocalUrl:
333
basepath = get_cache_dir()
334
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
191
337
class SqliteGitShaMap(GitShaMap):
193
339
def __init__(self, path=None):
245
381
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)
384
def lookup_blob_id(self, fileid, revision):
385
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
388
raise KeyError(fileid)
390
def lookup_tree_id(self, fileid, revision):
391
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
394
raise KeyError(fileid)
302
396
def lookup_git_sha(self, sha):
303
397
"""Lookup a Git sha in the database.
327
419
"""List the SHA1s."""
328
420
for table in ("blobs", "commits", "trees"):
330
for (row,) in self.db.execute("select sha1 from %s" % table):
335
TDB_HASH_SIZE = 50000
421
for (sha,) in self.db.execute("select sha1 from %s" % table):
425
class TdbCacheUpdater(CacheUpdater):
427
def __init__(self, cache, rev):
429
self.db = cache.idmap.db
430
self.revid = rev.revision_id
431
self.parent_revids = rev.parent_ids
435
def add_object(self, obj, ie):
436
sha = obj.sha().digest()
437
if obj.type_name == "commit":
438
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
439
type_data = (self.revid, obj.tree)
442
elif obj.type_name == "blob":
445
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
446
type_data = (ie.file_id, ie.revision)
447
elif obj.type_name == "tree":
450
type_data = (ie.file_id, self.revid)
453
self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
456
if self._commit is None:
457
raise AssertionError("No commit object added")
461
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
463
class TdbGitCacheFormat(BzrGitCacheFormat):
465
def get_format_string(self):
466
return 'bzr-git sha map version 3 using tdb\n'
468
def open(self, transport):
470
basepath = transport.local_abspath(".")
471
except bzrlib.errors.NotLocalUrl:
472
basepath = get_cache_dir()
474
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
477
"Unable to open existing bzr-git cache because 'tdb' is not "
338
481
class TdbGitShaMap(GitShaMap):
380
526
def __repr__(self):
381
527
return "%s(%r)" % (self.__class__.__name__, self.path)
384
def from_repository(cls, repository):
386
transport = getattr(repository, "_transport", None)
387
if transport is not None:
388
return cls(os.path.join(transport.local_abspath("."), "git.tdb"))
389
except bzrlib.errors.NotLocalUrl:
391
return cls(os.path.join(get_cache_dir(), "remote.tdb"))
393
529
def lookup_commit(self, revid):
394
530
return sha_to_hex(self.db["commit\0" + revid][:20])
396
def _add_entry(self, hexsha, type, type_data):
397
"""Add a new entry to the database.
402
sha = hex_to_sha(hexsha)
403
self.db["git\0" + sha] = "\0".join((type, type_data[0], type_data[1]))
405
self.db["commit\0" + type_data[0]] = "\0".join((sha, type_data[1]))
407
self.db["\0".join(("blob", type_data[0], type_data[1]))] = sha
409
def get_inventory_sha_map(self, revid):
411
class TdbInventorySHAMap(InventorySHAMap):
413
def __init__(self, db, revid):
417
def lookup_blob(self, fileid, revision):
418
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
532
def lookup_blob_id(self, fileid, revision):
533
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
420
return TdbInventorySHAMap(self.db, revid)
422
535
def lookup_git_sha(self, sha):
423
536
"""Lookup a Git sha in the database.
451
564
yield sha_to_hex(key[4:])
567
formats = registry.Registry()
568
formats.register(TdbGitCacheFormat().get_format_string(),
570
formats.register(SqliteGitCacheFormat().get_format_string(),
571
SqliteGitCacheFormat())
575
formats.register('default', SqliteGitCacheFormat())
577
formats.register('default', TdbGitCacheFormat())
580
def migrate_ancient_formats(repo_transport):
581
# Prefer migrating git.db over git.tdb, since the latter may not
582
# be openable on some platforms.
583
if repo_transport.has("git.db"):
584
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
585
repo_transport.rename("git.db", "git/idmap.db")
586
elif repo_transport.has("git.tdb"):
587
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
588
repo_transport.rename("git.tdb", "git/idmap.tdb")
591
def remove_readonly_transport_decorator(transport):
592
if transport.is_readonly():
593
return transport._decorated
454
597
def from_repository(repository):
456
return TdbGitShaMap.from_repository(repository)
458
return SqliteGitShaMap.from_repository(repository)
598
"""Open a cache file for a repository.
600
If the repository is remote and there is no transport available from it
601
this will use a local file in the users cache directory
602
(typically ~/.cache/bazaar/git/)
604
:param repository: A repository object
606
repo_transport = getattr(repository, "_transport", None)
607
if repo_transport is not None:
608
# Migrate older cache formats
609
repo_transport = remove_readonly_transport_decorator(repo_transport)
611
repo_transport.mkdir("git")
612
except bzrlib.errors.FileExists:
615
migrate_ancient_formats(repo_transport)
616
return BzrGitCacheFormat.from_repository(repository)