1
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Map from Git sha's to Bazaar objects."""
19
from dulwich.objects import (
34
from xdg.BaseDirectory import xdg_cache_home
36
from bzrlib.config import config_dir
37
ret = os.path.join(config_dir(), "git")
39
ret = os.path.join(xdg_cache_home, "bazaar", "git")
40
if not os.path.isdir(ret):
45
def check_pysqlite_version(sqlite3):
46
"""Check that sqlite library is compatible.
49
if (sqlite3.sqlite_version_info[0] < 3 or
50
(sqlite3.sqlite_version_info[0] == 3 and
51
sqlite3.sqlite_version_info[1] < 3)):
52
trace.warning('Needs at least sqlite 3.3.x')
53
raise bzrlib.errors.BzrError("incompatible sqlite library")
58
check_pysqlite_version(sqlite3)
59
except (ImportError, bzrlib.errors.BzrError), e:
60
from pysqlite2 import dbapi2 as sqlite3
61
check_pysqlite_version(sqlite3)
63
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
65
raise bzrlib.errors.BzrError("missing sqlite library")
68
_mapdbs = threading.local()
70
"""Get a cache for this thread's db connections."""
73
except AttributeError:
78
class GitShaMap(object):
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)
100
def lookup_git_sha(self, sha):
101
"""Lookup a Git sha in the database.
103
:param sha: Git object sha
104
:return: (type, type_data) with type_data:
105
revision: revid, tree sha
107
raise NotImplementedError(self.lookup_git_sha)
110
"""List the revision ids known."""
111
raise NotImplementedError(self.revids)
113
def missing_revisions(self, revids):
114
"""Return set of all the revisions that are not present."""
115
present_revids = set(self.revids())
116
if not isinstance(revids, set):
118
return revids - present_revids
121
"""List the SHA1s."""
122
raise NotImplementedError(self.sha1s)
124
def start_write_group(self):
125
"""Start writing changes."""
127
def commit_write_group(self):
128
"""Commit any pending changes."""
130
def abort_write_group(self):
131
"""Abort any pending changes."""
134
class DictGitShaMap(GitShaMap):
139
def add_entry(self, sha, type, type_data):
140
self.dict[sha] = (type, type_data)
142
def lookup_git_sha(self, sha):
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))
158
for key, (type, type_data) in self.dict.iteritems():
163
return self.dict.iterkeys()
166
class SqliteGitShaMap(GitShaMap):
168
def __init__(self, path=None):
171
self.db = sqlite3.connect(":memory:")
173
if not mapdbs().has_key(path):
174
mapdbs()[path] = sqlite3.connect(path)
175
self.db = mapdbs()[path]
176
self.db.text_factory = str
177
self.db.executescript("""
178
create table if not exists commits(
179
sha1 text not null check(length(sha1) == 40),
181
tree_sha text not null check(length(tree_sha) == 40)
183
create index if not exists commit_sha1 on commits(sha1);
184
create unique index if not exists commit_revid on commits(revid);
185
create table if not exists blobs(
186
sha1 text not null check(length(sha1) == 40),
187
fileid text not null,
190
create index if not exists blobs_sha1 on blobs(sha1);
191
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
192
create table if not exists trees(
193
sha1 text not null check(length(sha1) == 40),
194
fileid text not null,
197
create index if not exists trees_sha1 on trees(sha1);
198
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
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"))
211
def lookup_commit(self, revid):
212
row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
217
def commit_write_group(self):
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))
265
def lookup_git_sha(self, sha):
266
"""Lookup a Git sha in the database.
268
:param sha: Git object sha
269
:return: (type, type_data) with type_data:
270
revision: revid, tree sha
272
def format(type, row):
273
return (type, (row[0], row[1]))
274
row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
276
return format("commit", row)
277
row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
279
return format("blob", row)
280
row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
282
return format("tree", row)
286
"""List the revision ids known."""
287
return (row for (row,) in self.db.execute("select revid from commits"))
290
"""List the SHA1s."""
291
for table in ("blobs", "commits", "trees"):
293
for (row,) in self.db.execute("select sha1 from %s" % table):
298
TDB_HASH_SIZE = 50000
301
class TdbGitShaMap(GitShaMap):
302
"""SHA Map that uses a TDB database.
306
"git <sha1>" -> "<type> <type-data1> <type-data2>"
307
"commit revid" -> "<sha1> <tree-id>"
308
"tree fileid revid" -> "<sha1>"
309
"blob fileid revid" -> "<sha1>"
312
def __init__(self, path=None):
318
if not mapdbs().has_key(path):
319
mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
320
os.O_RDWR|os.O_CREAT)
321
self.db = mapdbs()[path]
323
if int(self.db["version"]) != TDB_MAP_VERSION:
324
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
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:
339
return cls(os.path.join(get_cache_dir(), "remote.tdb"))
341
def lookup_commit(self, revid):
342
return sha_to_hex(self.db["commit\0" + revid][:20])
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))])
367
def lookup_git_sha(self, sha):
368
"""Lookup a Git sha in the database.
370
:param sha: Git object sha
371
:return: (type, type_data) with type_data:
372
revision: revid, tree sha
375
sha = hex_to_sha(sha)
376
data = self.db["git\0" + sha].split("\0")
377
return (data[0], (data[1], data[2]))
379
def missing_revisions(self, revids):
382
if self.db.get("commit\0" + revid) is None:
387
"""List the revision ids known."""
388
for key in self.db.iterkeys():
389
if key.startswith("commit\0"):
393
"""List the SHA1s."""
394
for key in self.db.iterkeys():
395
if key.startswith("git\0"):
396
yield sha_to_hex(key[4:])