49
46
"""Check that sqlite library is compatible.
52
if (sqlite3.sqlite_version_info[0] < 3 or
53
(sqlite3.sqlite_version_info[0] == 3 and
49
if (sqlite3.sqlite_version_info[0] < 3 or
50
(sqlite3.sqlite_version_info[0] == 3 and
54
51
sqlite3.sqlite_version_info[1] < 3)):
55
warning('Needs at least sqlite 3.3.x')
52
trace.warning('Needs at least sqlite 3.3.x')
56
53
raise bzrlib.errors.BzrError("incompatible sqlite library")
61
58
check_pysqlite_version(sqlite3)
62
except (ImportError, bzrlib.errors.BzrError), e:
59
except (ImportError, bzrlib.errors.BzrError), e:
63
60
from pysqlite2 import dbapi2 as sqlite3
64
61
check_pysqlite_version(sqlite3)
66
warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
63
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
68
65
raise bzrlib.errors.BzrError("missing sqlite library")
78
75
return _mapdbs.cache
78
class InventorySHAMap(object):
79
"""Maps inventory file ids to Git SHAs."""
81
def lookup_blob(self, file_id, revision):
82
"""Retrieve a Git blob SHA by file id.
84
:param file_id: File id of the file/symlink
85
:param revision: revision in which the file was last changed.
87
raise NotImplementedError(self.lookup_blob)
89
def lookup_tree(self, file_id):
90
"""Retrieve a Git tree SHA by file id.
92
raise NotImplementedError(self.lookup_tree)
81
95
class GitShaMap(object):
82
96
"""Git<->Bzr revision id mapping database."""
84
def add_entry(self, sha, type, type_data):
98
def _add_entry(self, sha, type, type_data):
85
99
"""Add a new entry to the database.
87
raise NotImplementedError(self.add_entry)
101
raise NotImplementedError(self._add_entry)
89
def add_entries(self, entries):
103
def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
90
105
"""Add multiple new entries to the database.
95
def lookup_tree(self, fileid, revid):
96
"""Lookup the SHA of a git tree."""
97
raise NotImplementedError(self.lookup_tree)
99
def lookup_blob(self, fileid, revid):
100
"""Lookup a blob by the fileid it has in a bzr revision."""
101
raise NotImplementedError(self.lookup_blob)
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)
103
119
def lookup_git_sha(self, sha):
104
120
"""Lookup a Git sha in the database.
106
121
:param sha: Git object sha
107
122
:return: (type, type_data) with type_data:
108
123
revision: revid, tree sha
113
128
"""List the revision ids known."""
114
129
raise NotImplementedError(self.revids)
131
def missing_revisions(self, revids):
132
"""Return set of all the revisions that are not present."""
133
present_revids = set(self.revids())
134
if not isinstance(revids, set):
136
return revids - present_revids
117
139
"""List the SHA1s."""
118
140
raise NotImplementedError(self.sha1s)
142
def start_write_group(self):
143
"""Start writing changes."""
145
def commit_write_group(self):
121
146
"""Commit any pending changes."""
148
def abort_write_group(self):
149
"""Abort any pending changes."""
124
152
class DictGitShaMap(GitShaMap):
126
154
def __init__(self):
129
def add_entry(self, sha, type, type_data):
130
self.dict[sha] = (type, type_data)
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)
132
179
def lookup_git_sha(self, sha):
133
return self.dict[sha]
135
def lookup_tree(self, fileid, revid):
136
for k, v in self.dict.iteritems():
137
if v == ("tree", (fileid, revid)):
139
raise KeyError((fileid, revid))
141
def lookup_blob(self, fileid, revid):
142
for k, v in self.dict.iteritems():
143
if v == ("blob", (fileid, revid)):
145
raise KeyError((fileid, revid))
180
return self._by_sha[sha]
147
182
def revids(self):
148
for key, (type, type_data) in self.dict.iteritems():
183
for key, (type, type_data) in self._by_sha.iteritems():
149
184
if type == "commit":
150
185
yield type_data[0]
153
return self.dict.iterkeys()
188
return self._by_sha.iterkeys()
156
191
class SqliteGitShaMap(GitShaMap):
163
198
if not mapdbs().has_key(path):
164
199
mapdbs()[path] = sqlite3.connect(path)
165
self.db = mapdbs()[path]
200
self.db = mapdbs()[path]
201
self.db.text_factory = str
166
202
self.db.executescript("""
167
create table if not exists commits(sha1 text, revid text, tree_sha text);
203
create table if not exists commits(
204
sha1 text not null check(length(sha1) == 40),
206
tree_sha text not null check(length(tree_sha) == 40)
168
208
create index if not exists commit_sha1 on commits(sha1);
169
209
create unique index if not exists commit_revid on commits(revid);
170
create table if not exists blobs(sha1 text, fileid text, revid text);
210
create table if not exists blobs(
211
sha1 text not null check(length(sha1) == 40),
212
fileid text not null,
171
215
create index if not exists blobs_sha1 on blobs(sha1);
172
216
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
173
create table if not exists trees(sha1 text, fileid text, revid text);
174
create index if not exists trees_sha1 on trees(sha1);
217
create table if not exists trees(
218
sha1 text unique not null check(length(sha1) == 40),
219
fileid text not null,
222
create unique index if not exists trees_sha1 on trees(sha1);
175
223
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
227
return "%s(%r)" % (self.__class__.__name__, self.path)
179
230
def from_repository(cls, repository):
188
239
def lookup_commit(self, revid):
189
240
row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
190
241
if row is not None:
191
return row[0].encode("utf-8")
245
def commit_write_group(self):
197
def add_entries(self, entries):
248
def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
200
for sha, type, type_data in entries:
201
assert isinstance(type_data[0], str)
202
assert isinstance(type_data[1], str)
203
entry = (sha.decode("utf-8"), type_data[0].decode("utf-8"),
204
type_data[1].decode("utf-8"))
252
for (fileid, kind, hexsha, revision) in entries:
256
trees.append((hexsha, fileid, revid))
258
blobs.append((hexsha, fileid, revision))
210
260
raise AssertionError
212
262
self.db.executemany("replace into trees (sha1, fileid, revid) values (?, ?, ?)", trees)
214
264
self.db.executemany("replace into blobs (sha1, fileid, revid) values (?, ?, ?)", blobs)
217
def add_entry(self, sha, type, type_data):
265
self._add_entry(commit_sha, "commit", (revid, root_tree_sha))
267
def _add_entry(self, sha, type, type_data):
218
268
"""Add a new entry to the database.
220
270
assert isinstance(type_data, tuple)
221
273
assert isinstance(sha, str), "type was %r" % sha
222
274
if type == "commit":
223
275
self.db.execute("replace into commits (sha1, revid, tree_sha) values (?, ?, ?)", (sha, type_data[0], type_data[1]))
227
279
raise AssertionError("Unknown type %s" % type)
229
def lookup_tree(self, fileid, revid):
230
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid,revid)).fetchone()
232
raise KeyError((fileid, revid))
233
return row[0].encode("utf-8")
235
def lookup_blob(self, fileid, revid):
236
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revid)).fetchone()
238
raise KeyError((fileid, revid))
239
return row[0].encode("utf-8")
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)
241
302
def lookup_git_sha(self, sha):
242
303
"""Lookup a Git sha in the database.
246
307
revision: revid, tree sha
248
309
def format(type, row):
249
return (type, (row[0].encode("utf-8"), row[1].encode("utf-8")))
310
return (type, (row[0], row[1]))
250
311
row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
251
312
if row is not None:
252
313
return format("commit", row)
261
322
def revids(self):
262
323
"""List the revision ids known."""
263
for row in self.db.execute("select revid from commits").fetchall():
264
yield row[0].encode("utf-8")
324
return (row for (row,) in self.db.execute("select revid from commits"))
267
327
"""List the SHA1s."""
268
328
for table in ("blobs", "commits", "trees"):
269
for row in self.db.execute("select sha1 from %s" % table).fetchall():
270
yield row[0].encode("utf-8")
274
TDB_HASH_SIZE = 10000
330
for (row,) in self.db.execute("select sha1 from %s" % table):
335
TDB_HASH_SIZE = 50000
277
338
class TdbGitShaMap(GitShaMap):
294
355
if not mapdbs().has_key(path):
295
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
356
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
296
357
os.O_RDWR|os.O_CREAT)
297
self.db = mapdbs()[path]
298
if not "version" in self.db:
299
self.db["version"] = str(TDB_MAP_VERSION)
301
if int(self.db["version"]) != TDB_MAP_VERSION:
358
self.db = mapdbs()[path]
360
if int(self.db["version"]) not in (2, 3):
302
361
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
303
362
self.db["version"], TDB_MAP_VERSION)
305
self.db["version"] = str(TDB_MAP_VERSION)
366
self.db["version"] = str(TDB_MAP_VERSION)
368
def start_write_group(self):
369
"""Start writing changes."""
370
self.db.transaction_start()
372
def commit_write_group(self):
373
"""Commit any pending changes."""
374
self.db.transaction_commit()
376
def abort_write_group(self):
377
"""Abort any pending changes."""
378
self.db.transaction_cancel()
381
return "%s(%r)" % (self.__class__.__name__, self.path)
308
384
def from_repository(cls, repository):
317
393
def lookup_commit(self, revid):
318
394
return sha_to_hex(self.db["commit\0" + revid][:20])
323
def add_entry(self, sha, type, type_data):
396
def _add_entry(self, hexsha, type, type_data):
324
397
"""Add a new entry to the database.
326
self.db["git\0" + hex_to_sha(sha)] = "\0".join((type, type_data[0], type_data[1]))
402
sha = hex_to_sha(hexsha)
403
self.db["git\0" + sha] = "\0".join((type, type_data[0], type_data[1]))
327
404
if type == "commit":
328
self.db["commit\0" + type_data[0]] = "\0".join((hex_to_sha(sha), type_data[1]))
330
self.db["\0".join((type, type_data[0], type_data[1]))] = hex_to_sha(sha)
332
def lookup_tree(self, fileid, revid):
333
return sha_to_hex(self.db["\0".join(("tree", fileid, revid))])
335
def lookup_blob(self, fileid, revid):
336
return sha_to_hex(self.db["\0".join(("blob", fileid, revid))])
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))])
420
return TdbInventorySHAMap(self.db, revid)
338
422
def lookup_git_sha(self, sha):
339
423
"""Lookup a Git sha in the database.
342
426
:return: (type, type_data) with type_data:
343
427
revision: revid, tree sha
345
data = self.db["git\0" + hex_to_sha(sha)].split("\0")
430
sha = hex_to_sha(sha)
431
data = self.db["git\0" + sha].split("\0")
346
432
return (data[0], (data[1], data[2]))
434
def missing_revisions(self, revids):
437
if self.db.get("commit\0" + revid) is None:
348
441
def revids(self):
349
442
"""List the revision ids known."""
350
443
for key in self.db.iterkeys():