86
78
class GitShaMap(object):
87
79
"""Git<->Bzr revision id mapping database."""
81
def add_entry(self, sha, type, type_data):
82
"""Add a new entry to the database.
84
raise NotImplementedError(self.add_entry)
86
def add_entries(self, entries):
87
"""Add multiple new entries to the database.
92
def lookup_tree(self, fileid, revid):
93
"""Lookup the SHA of a git tree."""
94
raise NotImplementedError(self.lookup_tree)
96
def lookup_blob(self, fileid, revid):
97
"""Lookup a blob by the fileid it has in a bzr revision."""
98
raise NotImplementedError(self.lookup_blob)
89
100
def lookup_git_sha(self, sha):
90
101
"""Lookup a Git sha in the database.
91
103
:param sha: Git object sha
92
104
:return: (type, type_data) with type_data:
93
105
revision: revid, tree sha
95
107
raise NotImplementedError(self.lookup_git_sha)
97
def lookup_blob_id(self, file_id, revision):
98
"""Retrieve a Git blob SHA by file id.
100
:param file_id: File id of the file/symlink
101
:param revision: revision in which the file was last changed.
103
raise NotImplementedError(self.lookup_blob_id)
105
def lookup_tree_id(self, file_id, revision):
106
"""Retrieve a Git tree SHA by file id.
108
raise NotImplementedError(self.lookup_tree_id)
110
109
def revids(self):
111
110
"""List the revision ids known."""
112
111
raise NotImplementedError(self.revids)
132
131
"""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")
255
134
class DictGitShaMap(GitShaMap):
257
136
def __init__(self):
262
def lookup_blob_id(self, fileid, revision):
263
return self._by_fileid[revision][fileid]
139
def add_entry(self, sha, type, type_data):
140
self.dict[sha] = (type, type_data)
265
142
def lookup_git_sha(self, sha):
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]
143
return self.dict[sha]
145
def lookup_tree(self, fileid, revid):
146
for k, v in self.dict.iteritems():
147
if v == ("tree", (fileid, revid)):
149
raise KeyError((fileid, revid))
151
def lookup_blob(self, fileid, revid):
152
for k, v in self.dict.iteritems():
153
if v == ("blob", (fileid, revid)):
155
raise KeyError((fileid, revid))
274
157
def revids(self):
275
for key, (type, type_data) in self._by_sha.iteritems():
158
for key, (type, type_data) in self.dict.iteritems():
276
159
if type == "commit":
277
160
yield type_data[0]
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"))
163
return self.dict.iterkeys()
337
166
class SqliteGitShaMap(GitShaMap):
381
217
def commit_write_group(self):
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)
220
def add_entries(self, entries):
223
for sha, type, type_data in entries:
224
assert isinstance(type_data[0], str)
225
assert isinstance(type_data[1], str)
226
entry = (sha, type_data[0], type_data[1])
234
self.db.executemany("replace into trees (sha1, fileid, revid) values (?, ?, ?)", trees)
236
self.db.executemany("replace into blobs (sha1, fileid, revid) values (?, ?, ?)", blobs)
239
def add_entry(self, sha, type, type_data):
240
"""Add a new entry to the database.
242
assert isinstance(type_data, tuple)
245
assert isinstance(sha, str), "type was %r" % sha
247
self.db.execute("replace into commits (sha1, revid, tree_sha) values (?, ?, ?)", (sha, type_data[0], type_data[1]))
248
elif type in ("blob", "tree"):
249
self.db.execute("replace into %ss (sha1, fileid, revid) values (?, ?, ?)" % type, (sha, type_data[0], type_data[1]))
251
raise AssertionError("Unknown type %s" % type)
253
def lookup_tree(self, fileid, revid):
254
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid,revid)).fetchone()
256
raise KeyError((fileid, revid))
259
def lookup_blob(self, fileid, revid):
260
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revid)).fetchone()
262
raise KeyError((fileid, revid))
396
265
def lookup_git_sha(self, sha):
397
266
"""Lookup a Git sha in the database.
400
269
:return: (type, type_data) with type_data:
401
270
revision: revid, tree sha
272
def format(type, row):
273
return (type, (row[0], row[1]))
403
274
row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
404
275
if row is not None:
405
return ("commit", row)
276
return format("commit", row)
406
277
row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
407
278
if row is not None:
279
return format("blob", row)
409
280
row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
410
281
if row is not None:
282
return format("tree", row)
412
283
raise KeyError(sha)
414
285
def revids(self):
415
286
"""List the revision ids known."""
416
return (row for (row,) in self.db.execute("select revid from commits"))
287
for row in self.db.execute("select revid from commits").fetchall():
419
291
"""List the SHA1s."""
420
292
for table in ("blobs", "commits", "trees"):
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 "
293
for row in self.db.execute("select sha1 from %s" % table).fetchall():
298
TDB_HASH_SIZE = 50000
481
301
class TdbGitShaMap(GitShaMap):
501
318
if not mapdbs().has_key(path):
502
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
319
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
503
320
os.O_RDWR|os.O_CREAT)
504
321
self.db = mapdbs()[path]
506
if int(self.db["version"]) not in (2, 3):
323
if int(self.db["version"]) != TDB_MAP_VERSION:
507
324
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
508
self.db["version"], self.TDB_MAP_VERSION)
325
self.db["version"], TDB_MAP_VERSION)
327
self.db["version"] = str(TDB_MAP_VERSION)
329
self.db["version"] = str(TDB_MAP_VERSION)
332
def from_repository(cls, repository):
334
transport = getattr(repository, "_transport", None)
335
if transport is not None:
336
return cls(os.path.join(transport.local_abspath("."), "git.tdb"))
337
except bzrlib.errors.NotLocalUrl:
512
self.db["version"] = str(self.TDB_MAP_VERSION)
514
def start_write_group(self):
515
"""Start writing changes."""
516
self.db.transaction_start()
518
def commit_write_group(self):
519
"""Commit any pending changes."""
520
self.db.transaction_commit()
522
def abort_write_group(self):
523
"""Abort any pending changes."""
524
self.db.transaction_cancel()
527
return "%s(%r)" % (self.__class__.__name__, self.path)
339
return cls(os.path.join(get_cache_dir(), "remote.tdb"))
529
341
def lookup_commit(self, revid):
530
342
return sha_to_hex(self.db["commit\0" + revid][:20])
532
def lookup_blob_id(self, fileid, revision):
533
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
344
def add_entry(self, hexsha, type, type_data):
345
"""Add a new entry to the database.
350
sha = hex_to_sha(hexsha)
351
self.db["git\0" + sha] = "\0".join((type, type_data[0], type_data[1]))
353
self.db["commit\0" + type_data[0]] = "\0".join((sha, type_data[1]))
355
self.db["\0".join((type, type_data[0], type_data[1]))] = sha
357
def lookup_tree(self, fileid, revid):
358
sha = self.db["\0".join(("tree", fileid, revid))]
362
return sha_to_hex(sha)
364
def lookup_blob(self, fileid, revid):
365
return sha_to_hex(self.db["\0".join(("blob", fileid, revid))])
535
367
def lookup_git_sha(self, sha):
536
368
"""Lookup a Git sha in the database.
562
394
for key in self.db.iterkeys():
563
395
if key.startswith("git\0"):
564
396
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
597
def 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)