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
raise NotImplementedError(self.get_format_string)
148
def open(self, transport):
149
raise NotImplementedError(self.open)
151
def initialize(self, transport):
152
transport.put_bytes('format', self.get_format_string())
155
def from_repository(self, repository):
156
repo_transport = getattr(repository, "_transport", None)
157
if repo_transport is not None:
159
repo_transport.mkdir('git')
160
except bzrlib.errors.FileExists:
162
transport = repo_transport.clone('git')
164
transport = get_remote_cache_transport()
166
format_name = transport.get_bytes('format')
167
format = formats.get(format_name)
168
except bzrlib.errors.NoSuchFile:
169
format = formats.get('default')
170
format.initialize(transport)
171
return format.open(transport)
174
class CacheUpdater(object):
176
def add_object(self, obj, ie):
177
raise NotImplementedError(self.add_object)
180
raise NotImplementedError(self.finish)
183
class BzrGitCache(object):
184
"""Caching backend."""
186
def __init__(self, idmap, content_cache, cache_updater_klass):
188
self.content_cache = content_cache
189
self._cache_updater_klass = cache_updater_klass
191
def get_updater(self, rev):
192
return self._cache_updater_klass(self, rev)
195
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
198
class DictCacheUpdater(CacheUpdater):
200
def __init__(self, cache, rev):
202
self.revid = rev.revision_id
203
self.parent_revids = rev.parent_ids
207
def add_object(self, obj, ie):
208
if obj.type_name == "commit":
211
type_data = (self.revid, self._commit.tree)
212
elif obj.type_name in ("blob", "tree"):
213
if obj.type_name == "blob":
214
revision = ie.revision
216
revision = self.revid
217
type_data = (ie.file_id, revision)
218
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
221
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
224
if self._commit is None:
225
raise AssertionError("No commit object added")
229
134
class DictGitShaMap(GitShaMap):
231
136
def __init__(self):
235
def lookup_blob_id(self, fileid, revision):
236
return self._by_fileid[revision][fileid]
139
def add_entry(self, sha, type, type_data):
140
self.dict[sha] = (type, type_data)
238
142
def lookup_git_sha(self, sha):
239
return self._by_sha[sha]
241
def lookup_tree_id(self, fileid, revision):
242
return self._base._by_fileid[revision][fileid]
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))
244
157
def revids(self):
245
for key, (type, type_data) in self._by_sha.iteritems():
158
for key, (type, type_data) in self.dict.iteritems():
246
159
if type == "commit":
247
160
yield type_data[0]
250
return self._by_sha.iterkeys()
253
class SqliteCacheUpdater(CacheUpdater):
255
def __init__(self, cache, rev):
257
self.db = self.cache.idmap.db
258
self.revid = rev.revision_id
263
def add_object(self, obj, ie):
264
if obj.type_name == "commit":
267
elif obj.type_name == "tree":
268
self._trees.append((obj.id, ie.file_id, self.revid))
269
elif obj.type_name == "blob":
270
self._blobs.append((obj.id, ie.file_id, ie.revision))
275
if self._commit is None:
276
raise AssertionError("No commit object added")
278
"replace into trees (sha1, fileid, revid) values (?, ?, ?)",
281
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
284
"replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
285
(self._commit.id, self.revid, self._commit.tree))
289
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
292
class SqliteGitCacheFormat(BzrGitCacheFormat):
294
def get_format_string(self):
295
return 'bzr-git sha map version 1 using sqlite\n'
297
def open(self, transport):
299
basepath = transport.local_abspath(".")
300
except bzrlib.errors.NotLocalUrl:
301
basepath = get_cache_dir()
302
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
163
return self.dict.iterkeys()
305
166
class SqliteGitShaMap(GitShaMap):
329
190
create index if not exists blobs_sha1 on blobs(sha1);
330
191
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
331
192
create table if not exists trees(
332
sha1 text unique not null check(length(sha1) == 40),
193
sha1 text not null check(length(sha1) == 40),
333
194
fileid text not null,
334
195
revid text not null
336
create unique index if not exists trees_sha1 on trees(sha1);
197
create index if not exists trees_sha1 on trees(sha1);
337
198
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
341
return "%s(%r)" % (self.__class__.__name__, self.path)
202
def from_repository(cls, repository):
204
transport = getattr(repository, "_transport", None)
205
if transport is not None:
206
return cls(os.path.join(transport.local_abspath("."), "git.db"))
207
except bzrlib.errors.NotLocalUrl:
209
return cls(os.path.join(get_cache_dir(), "remote.db"))
343
211
def lookup_commit(self, revid):
344
212
row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
345
213
if row is not None:
349
217
def commit_write_group(self):
352
def lookup_blob_id(self, fileid, revision):
353
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
356
raise KeyError(fileid)
358
def lookup_tree_id(self, fileid, revision):
359
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, self.revid)).fetchone()
362
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))
364
265
def lookup_git_sha(self, sha):
365
266
"""Lookup a Git sha in the database.
368
269
:return: (type, type_data) with type_data:
369
270
revision: revid, tree sha
272
def format(type, row):
273
return (type, (row[0], row[1]))
371
274
row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
372
275
if row is not None:
373
return ("commit", row)
276
return format("commit", row)
374
277
row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
375
278
if row is not None:
279
return format("blob", row)
377
280
row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
378
281
if row is not None:
282
return format("tree", row)
380
283
raise KeyError(sha)
382
285
def revids(self):
383
286
"""List the revision ids known."""
384
return (row for (row,) in self.db.execute("select revid from commits"))
287
for row in self.db.execute("select revid from commits").fetchall():
387
291
"""List the SHA1s."""
388
292
for table in ("blobs", "commits", "trees"):
389
for (sha,) in self.db.execute("select sha1 from %s" % table):
393
class TdbCacheUpdater(CacheUpdater):
395
def __init__(self, cache, rev):
397
self.db = cache.idmap.db
398
self.revid = rev.revision_id
399
self.parent_revids = rev.parent_ids
403
def add_object(self, obj, ie):
404
sha = obj.sha().digest()
405
if obj.type_name == "commit":
406
self.db["commit\0" + self.revid] = "\0".join((obj.id, obj.tree))
407
type_data = (self.revid, obj.tree)
410
elif obj.type_name == "blob":
411
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
412
type_data = (ie.file_id, ie.revision)
413
elif obj.type_name == "tree":
414
type_data = (ie.file_id, self.revid)
417
self.db["git\0" + sha] = "\0".join((obj.type_name,
418
type_data[0], type_data[1]))
421
if self._commit is None:
422
raise AssertionError("No commit object added")
426
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
428
class TdbGitCacheFormat(BzrGitCacheFormat):
430
def get_format_string(self):
431
return 'bzr-git sha map version 3 using tdb\n'
433
def open(self, transport):
435
basepath = transport.local_abspath(".")
436
except bzrlib.errors.NotLocalUrl:
437
basepath = get_cache_dir()
439
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
442
"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
446
301
class TdbGitShaMap(GitShaMap):
466
318
if not mapdbs().has_key(path):
467
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
319
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
468
320
os.O_RDWR|os.O_CREAT)
469
321
self.db = mapdbs()[path]
471
if int(self.db["version"]) not in (2, 3):
323
if int(self.db["version"]) != TDB_MAP_VERSION:
472
324
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
473
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:
477
self.db["version"] = str(self.TDB_MAP_VERSION)
479
def start_write_group(self):
480
"""Start writing changes."""
481
self.db.transaction_start()
483
def commit_write_group(self):
484
"""Commit any pending changes."""
485
self.db.transaction_commit()
487
def abort_write_group(self):
488
"""Abort any pending changes."""
489
self.db.transaction_cancel()
492
return "%s(%r)" % (self.__class__.__name__, self.path)
339
return cls(os.path.join(get_cache_dir(), "remote.tdb"))
494
341
def lookup_commit(self, revid):
495
342
return sha_to_hex(self.db["commit\0" + revid][:20])
497
def lookup_blob_id(self, fileid, revision):
498
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))])
500
367
def lookup_git_sha(self, sha):
501
368
"""Lookup a Git sha in the database.
527
394
for key in self.db.iterkeys():
528
395
if key.startswith("git\0"):
529
396
yield sha_to_hex(key[4:])
532
formats = registry.Registry()
533
formats.register(TdbGitCacheFormat().get_format_string(),
535
formats.register(SqliteGitCacheFormat().get_format_string(),
536
SqliteGitCacheFormat())
540
formats.register('default', SqliteGitCacheFormat())
542
formats.register('default', TdbGitCacheFormat())
545
def migrate_ancient_formats(repo_transport):
546
if repo_transport.has("git.tdb"):
547
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
548
repo_transport.rename("git.tdb", "git/idmap.tdb")
549
elif repo_transport.has("git.db"):
550
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
551
repo_transport.rename("git.db", "git/idmap.db")
554
def from_repository(repository):
555
repo_transport = getattr(repository, "_transport", None)
556
if repo_transport is not None:
557
# Migrate older cache formats
559
repo_transport.mkdir("git")
560
except bzrlib.errors.FileExists:
563
migrate_ancient_formats(repo_transport)
564
return BzrGitCacheFormat.from_repository(repository)