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_hint=None):
82
"""Retrieve a Git blob SHA by file id.
84
:param file_id: File id of the file/symlink
85
:param revision_hint: Optional revision in which the file was last
88
raise NotImplementedError(self.lookup_blob)
90
def lookup_tree(self, file_id):
91
"""Retrieve a Git tree SHA by file id.
93
raise NotImplementedError(self.lookup_tree)
81
96
class GitShaMap(object):
82
97
"""Git<->Bzr revision id mapping database."""
84
def add_entry(self, sha, type, type_data):
99
def _add_entry(self, sha, type, type_data):
85
100
"""Add a new entry to the database.
87
raise NotImplementedError(self.add_entry)
102
raise NotImplementedError(self._add_entry)
89
def add_entries(self, entries):
104
def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
90
106
"""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)
108
self._add_entry(commit_sha, "commit", (revid, root_tree_sha))
109
for (fileid, kind, hexsha, revision) in entries:
110
self._add_entry(hexsha, kind, (fileid, revision))
112
def get_inventory_sha_map(self, revid):
113
"""Return the inventory SHA map for a revision.
115
:param revid: Revision to fetch the map for
116
:return: A `InventorySHAMap`
118
raise NotImplementedError(self.get_inventory_sha_map)
103
120
def lookup_git_sha(self, sha):
104
121
"""Lookup a Git sha in the database.
106
122
:param sha: Git object sha
107
123
:return: (type, type_data) with type_data:
108
124
revision: revid, tree sha
113
129
"""List the revision ids known."""
114
130
raise NotImplementedError(self.revids)
132
def missing_revisions(self, revids):
133
"""Return set of all the revisions that are not present."""
134
present_revids = set(self.revids())
135
if not isinstance(revids, set):
137
return revids - present_revids
117
140
"""List the SHA1s."""
118
141
raise NotImplementedError(self.sha1s)
143
def start_write_group(self):
144
"""Start writing changes."""
146
def commit_write_group(self):
121
147
"""Commit any pending changes."""
149
def abort_write_group(self):
150
"""Abort any pending changes."""
124
153
class DictGitShaMap(GitShaMap):
126
155
def __init__(self):
129
def add_entry(self, sha, type, type_data):
130
self.dict[sha] = (type, type_data)
159
def _add_entry(self, sha, type, type_data):
160
self._by_sha[sha] = (type, type_data)
161
if type in ("blob", "tree"):
162
self._by_fileid.setdefault(type_data[1], {})[type_data[0]] = sha
164
def get_inventory_sha_map(self, revid):
165
class DictInventorySHAMap(InventorySHAMap):
167
def __init__(self, base, revid):
171
def lookup_blob(self, fileid, revision_hint=None):
172
if revision_hint is not None:
173
revid = revision_hint
176
return self._base._by_fileid[revid][fileid]
178
def lookup_tree(self, fileid):
179
return self._base._by_fileid[self.revid][fileid]
181
return DictInventorySHAMap(self, revid)
132
183
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))
184
return self._by_sha[sha]
147
186
def revids(self):
148
for key, (type, type_data) in self.dict.iteritems():
187
for key, (type, type_data) in self._by_sha.iteritems():
149
188
if type == "commit":
150
189
yield type_data[0]
153
return self.dict.iterkeys()
192
return self._by_sha.iterkeys()
156
195
class SqliteGitShaMap(GitShaMap):
163
202
if not mapdbs().has_key(path):
164
203
mapdbs()[path] = sqlite3.connect(path)
165
self.db = mapdbs()[path]
204
self.db = mapdbs()[path]
205
self.db.text_factory = str
166
206
self.db.executescript("""
167
create table if not exists commits(sha1 text, revid text, tree_sha text);
207
create table if not exists commits(
208
sha1 text not null check(length(sha1) == 40),
210
tree_sha text not null check(length(tree_sha) == 40)
168
212
create index if not exists commit_sha1 on commits(sha1);
169
213
create unique index if not exists commit_revid on commits(revid);
170
create table if not exists blobs(sha1 text, fileid text, revid text);
214
create table if not exists blobs(
215
sha1 text not null check(length(sha1) == 40),
216
fileid text not null,
171
219
create index if not exists blobs_sha1 on blobs(sha1);
172
220
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);
221
create table if not exists trees(
222
sha1 text unique not null check(length(sha1) == 40),
223
fileid text not null,
226
create unique index if not exists trees_sha1 on trees(sha1);
175
227
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
231
return "%s(%r)" % (self.__class__.__name__, self.path)
179
234
def from_repository(cls, repository):
188
243
def lookup_commit(self, revid):
189
244
row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
190
245
if row is not None:
191
return row[0].encode("utf-8")
249
def commit_write_group(self):
197
def add_entries(self, entries):
252
def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
254
self._add_entry(commit_sha, "commit", (revid, 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"))
257
for (fileid, kind, hexsha, revision) in entries:
261
trees.append((hexsha, "tree", (fileid, revid)))
263
blobs.append((hexsha, (fileid, revision)))
210
265
raise AssertionError
214
269
self.db.executemany("replace into blobs (sha1, fileid, revid) values (?, ?, ?)", blobs)
217
def add_entry(self, sha, type, type_data):
272
def _add_entry(self, sha, type, type_data):
218
273
"""Add a new entry to the database.
220
275
assert isinstance(type_data, tuple)
221
278
assert isinstance(sha, str), "type was %r" % sha
222
279
if type == "commit":
223
280
self.db.execute("replace into commits (sha1, revid, tree_sha) values (?, ?, ?)", (sha, type_data[0], type_data[1]))
227
284
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")
286
def get_inventory_sha_map(self, revid):
287
class SqliteInventorySHAMap(InventorySHAMap):
289
def __init__(self, db, revid):
293
def lookup_blob(self, fileid, revision_hint=None):
294
if revision_hint is not None:
295
revid = revision_hint
298
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revid)).fetchone()
301
raise KeyError(fileid)
303
def lookup_tree(self, fileid):
304
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, self.revid)).fetchone()
307
raise KeyError(fileid)
309
return SqliteInventorySHAMap(self.db, revid)
241
311
def lookup_git_sha(self, sha):
242
312
"""Lookup a Git sha in the database.
246
316
revision: revid, tree sha
248
318
def format(type, row):
249
return (type, (row[0].encode("utf-8"), row[1].encode("utf-8")))
319
return (type, (row[0], row[1]))
250
320
row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
251
321
if row is not None:
252
322
return format("commit", row)
261
331
def revids(self):
262
332
"""List the revision ids known."""
263
for row in self.db.execute("select revid from commits").fetchall():
264
yield row[0].encode("utf-8")
333
return (row for (row,) in self.db.execute("select revid from commits"))
267
336
"""List the SHA1s."""
268
337
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
339
for (row,) in self.db.execute("select sha1 from %s" % table):
344
TDB_HASH_SIZE = 50000
277
347
class TdbGitShaMap(GitShaMap):
294
364
if not mapdbs().has_key(path):
295
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
365
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
296
366
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:
367
self.db = mapdbs()[path]
369
if int(self.db["version"]) not in (2, 3):
302
370
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
303
371
self.db["version"], TDB_MAP_VERSION)
305
self.db["version"] = str(TDB_MAP_VERSION)
375
self.db["version"] = str(TDB_MAP_VERSION)
378
return "%s(%r)" % (self.__class__.__name__, self.path)
308
381
def from_repository(cls, repository):
317
390
def lookup_commit(self, revid):
318
391
return sha_to_hex(self.db["commit\0" + revid][:20])
323
def add_entry(self, sha, type, type_data):
393
def _add_entry(self, hexsha, type, type_data):
324
394
"""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]))
399
sha = hex_to_sha(hexsha)
400
self.db["git\0" + sha] = "\0".join((type, type_data[0], type_data[1]))
327
401
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))])
402
self.db["commit\0" + type_data[0]] = "\0".join((sha, type_data[1]))
404
self.db["\0".join(("blob", type_data[0], type_data[1]))] = sha
406
def get_inventory_sha_map(self, revid):
408
class TdbInventorySHAMap(InventorySHAMap):
410
def __init__(self, db, revid):
414
def lookup_blob(self, fileid, revision_hint=None):
415
if revision_hint is not None:
416
revid = revision_hint
419
return sha_to_hex(self.db["\0".join(("blob", fileid, revid))])
421
return TdbInventorySHAMap(self.db, revid)
338
423
def lookup_git_sha(self, sha):
339
424
"""Lookup a Git sha in the database.
342
427
:return: (type, type_data) with type_data:
343
428
revision: revid, tree sha
345
data = self.db["git\0" + hex_to_sha(sha)].split("\0")
431
sha = hex_to_sha(sha)
432
data = self.db["git\0" + sha].split("\0")
346
433
return (data[0], (data[1], data[2]))
435
def missing_revisions(self, revids):
438
if self.db.get("commit\0" + revid) is None:
348
442
def revids(self):
349
443
"""List the revision ids known."""
350
444
for key in self.db.iterkeys():