/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to shamap.py

  • Committer: Martin Pool
  • Date: 2005-08-04 22:04:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050804220440-99562df8151d1ac5
- add pending merge from aaron

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
2
 
#
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.
7
 
#
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.
12
 
#
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
16
 
 
17
 
"""Map from Git sha's to Bazaar objects."""
18
 
 
19
 
from dulwich.objects import (
20
 
    sha_to_hex,
21
 
    hex_to_sha,
22
 
    )
23
 
import os
24
 
import threading
25
 
 
26
 
import bzrlib
27
 
from bzrlib import (
28
 
    trace,
29
 
    )
30
 
 
31
 
 
32
 
def get_cache_dir():
33
 
    try:
34
 
        from xdg.BaseDirectory import xdg_cache_home
35
 
    except ImportError:
36
 
        from bzrlib.config import config_dir
37
 
        ret = os.path.join(config_dir(), "git")
38
 
    else:
39
 
        ret = os.path.join(xdg_cache_home, "bazaar", "git")
40
 
    if not os.path.isdir(ret):
41
 
        os.makedirs(ret)
42
 
    return ret
43
 
 
44
 
 
45
 
def check_pysqlite_version(sqlite3):
46
 
    """Check that sqlite library is compatible.
47
 
 
48
 
    """
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")
54
 
 
55
 
try:
56
 
    try:
57
 
        import sqlite3
58
 
        check_pysqlite_version(sqlite3)
59
 
    except (ImportError, bzrlib.errors.BzrError), e:
60
 
        from pysqlite2 import dbapi2 as sqlite3
61
 
        check_pysqlite_version(sqlite3)
62
 
except:
63
 
    trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
64
 
            'module')
65
 
    raise bzrlib.errors.BzrError("missing sqlite library")
66
 
 
67
 
 
68
 
_mapdbs = threading.local()
69
 
def mapdbs():
70
 
    """Get a cache for this thread's db connections."""
71
 
    try:
72
 
        return _mapdbs.cache
73
 
    except AttributeError:
74
 
        _mapdbs.cache = {}
75
 
        return _mapdbs.cache
76
 
 
77
 
 
78
 
class InventorySHAMap(object):
79
 
    """Maps inventory file ids to Git SHAs."""
80
 
 
81
 
    def lookup_blob(self, file_id, revision):
82
 
        """Retrieve a Git blob SHA by file id.
83
 
 
84
 
        :param file_id: File id of the file/symlink
85
 
        :param revision: revision in which the file was last changed.
86
 
        """
87
 
        raise NotImplementedError(self.lookup_blob)
88
 
 
89
 
    def lookup_tree(self, file_id):
90
 
        """Retrieve a Git tree SHA by file id.
91
 
        """
92
 
        raise NotImplementedError(self.lookup_tree)
93
 
 
94
 
 
95
 
class GitShaMap(object):
96
 
    """Git<->Bzr revision id mapping database."""
97
 
 
98
 
    def _add_entry(self, sha, type, type_data):
99
 
        """Add a new entry to the database.
100
 
        """
101
 
        raise NotImplementedError(self._add_entry)
102
 
 
103
 
    def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha, 
104
 
                    entries):
105
 
        """Add multiple new entries to the database.
106
 
        """
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))
110
 
 
111
 
    def get_inventory_sha_map(self, revid):
112
 
        """Return the inventory SHA map for a revision.
113
 
 
114
 
        :param revid: Revision to fetch the map for
115
 
        :return: A `InventorySHAMap`
116
 
        """
117
 
        raise NotImplementedError(self.get_inventory_sha_map)
118
 
 
119
 
    def lookup_git_sha(self, sha):
120
 
        """Lookup a Git sha in the database.
121
 
        :param sha: Git object sha
122
 
        :return: (type, type_data) with type_data:
123
 
            revision: revid, tree sha
124
 
        """
125
 
        raise NotImplementedError(self.lookup_git_sha)
126
 
 
127
 
    def revids(self):
128
 
        """List the revision ids known."""
129
 
        raise NotImplementedError(self.revids)
130
 
 
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):
135
 
            revids = set(revids)
136
 
        return revids - present_revids
137
 
 
138
 
    def sha1s(self):
139
 
        """List the SHA1s."""
140
 
        raise NotImplementedError(self.sha1s)
141
 
 
142
 
    def start_write_group(self):
143
 
        """Start writing changes."""
144
 
 
145
 
    def commit_write_group(self):
146
 
        """Commit any pending changes."""
147
 
 
148
 
    def abort_write_group(self):
149
 
        """Abort any pending changes."""
150
 
 
151
 
 
152
 
class DictGitShaMap(GitShaMap):
153
 
 
154
 
    def __init__(self):
155
 
        self._by_sha = {}
156
 
        self._by_fileid = {}
157
 
 
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
162
 
 
163
 
    def get_inventory_sha_map(self, revid):
164
 
 
165
 
        class DictInventorySHAMap(InventorySHAMap):
166
 
 
167
 
            def __init__(self, base, revid):
168
 
                self._base = base
169
 
                self.revid = revid
170
 
 
171
 
            def lookup_blob(self, fileid, revision):
172
 
                return self._base._by_fileid[revision][fileid]
173
 
 
174
 
            def lookup_tree(self, fileid):
175
 
                return self._base._by_fileid[self.revid][fileid]
176
 
 
177
 
        return DictInventorySHAMap(self, revid)
178
 
 
179
 
    def lookup_git_sha(self, sha):
180
 
        return self._by_sha[sha]
181
 
 
182
 
    def revids(self):
183
 
        for key, (type, type_data) in self._by_sha.iteritems():
184
 
            if type == "commit":
185
 
                yield type_data[0]
186
 
 
187
 
    def sha1s(self):
188
 
        return self._by_sha.iterkeys()
189
 
 
190
 
 
191
 
class SqliteGitShaMap(GitShaMap):
192
 
 
193
 
    def __init__(self, path=None):
194
 
        self.path = path
195
 
        if path is None:
196
 
            self.db = sqlite3.connect(":memory:")
197
 
        else:
198
 
            if not mapdbs().has_key(path):
199
 
                mapdbs()[path] = sqlite3.connect(path)
200
 
            self.db = mapdbs()[path]
201
 
        self.db.text_factory = str
202
 
        self.db.executescript("""
203
 
        create table if not exists commits(
204
 
            sha1 text not null check(length(sha1) == 40),
205
 
            revid text not null,
206
 
            tree_sha text not null check(length(tree_sha) == 40)
207
 
        );
208
 
        create index if not exists commit_sha1 on commits(sha1);
209
 
        create unique index if not exists commit_revid on commits(revid);
210
 
        create table if not exists blobs(
211
 
            sha1 text not null check(length(sha1) == 40),
212
 
            fileid text not null,
213
 
            revid text not null
214
 
        );
215
 
        create index if not exists blobs_sha1 on blobs(sha1);
216
 
        create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
217
 
        create table if not exists trees(
218
 
            sha1 text unique not null check(length(sha1) == 40),
219
 
            fileid text not null,
220
 
            revid text not null
221
 
        );
222
 
        create unique index if not exists trees_sha1 on trees(sha1);
223
 
        create unique index if not exists trees_fileid_revid on trees(fileid, revid);
224
 
""")
225
 
 
226
 
    def __repr__(self):
227
 
        return "%s(%r)" % (self.__class__.__name__, self.path)
228
 
    
229
 
    @classmethod
230
 
    def from_repository(cls, repository):
231
 
        try:
232
 
            transport = getattr(repository, "_transport", None)
233
 
            if transport is not None:
234
 
                return cls(os.path.join(transport.local_abspath("."), "git.db"))
235
 
        except bzrlib.errors.NotLocalUrl:
236
 
            pass
237
 
        return cls(os.path.join(get_cache_dir(), "remote.db"))
238
 
 
239
 
    def lookup_commit(self, revid):
240
 
        row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
241
 
        if row is not None:
242
 
            return row[0]
243
 
        raise KeyError
244
 
 
245
 
    def commit_write_group(self):
246
 
        self.db.commit()
247
 
 
248
 
    def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha,
249
 
                    entries):
250
 
        trees = []
251
 
        blobs = []
252
 
        for (fileid, kind, hexsha, revision) in entries:
253
 
            if kind is None:
254
 
                continue
255
 
            if kind == "tree":
256
 
                trees.append((hexsha, fileid, revid))
257
 
            elif kind == "blob":
258
 
                blobs.append((hexsha, fileid, revision))
259
 
            else:
260
 
                raise AssertionError
261
 
        if trees:
262
 
            self.db.executemany("replace into trees (sha1, fileid, revid) values (?, ?, ?)", trees)
263
 
        if blobs:
264
 
            self.db.executemany("replace into blobs (sha1, fileid, revid) values (?, ?, ?)", blobs)
265
 
        self._add_entry(commit_sha, "commit", (revid, root_tree_sha))
266
 
 
267
 
    def _add_entry(self, sha, type, type_data):
268
 
        """Add a new entry to the database.
269
 
        """
270
 
        assert isinstance(type_data, tuple)
271
 
        if sha is None:
272
 
            return
273
 
        assert isinstance(sha, str), "type was %r" % sha
274
 
        if type == "commit":
275
 
            self.db.execute("replace into commits (sha1, revid, tree_sha) values (?, ?, ?)", (sha, type_data[0], type_data[1]))
276
 
        elif type in ("blob", "tree"):
277
 
            self.db.execute("replace into %ss (sha1, fileid, revid) values (?, ?, ?)" % type, (sha, type_data[0], type_data[1]))
278
 
        else:
279
 
            raise AssertionError("Unknown type %s" % type)
280
 
 
281
 
    def get_inventory_sha_map(self, revid):
282
 
        class SqliteInventorySHAMap(InventorySHAMap):
283
 
 
284
 
            def __init__(self, db, revid):
285
 
                self.db = db
286
 
                self.revid = revid
287
 
 
288
 
            def lookup_blob(self, fileid, revision):
289
 
                row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
290
 
                if row is not None:
291
 
                    return row[0]
292
 
                raise KeyError(fileid)
293
 
 
294
 
            def lookup_tree(self, fileid):
295
 
                row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, self.revid)).fetchone()
296
 
                if row is not None:
297
 
                    return row[0]
298
 
                raise KeyError(fileid)
299
 
 
300
 
        return SqliteInventorySHAMap(self.db, revid)
301
 
 
302
 
    def lookup_git_sha(self, sha):
303
 
        """Lookup a Git sha in the database.
304
 
 
305
 
        :param sha: Git object sha
306
 
        :return: (type, type_data) with type_data:
307
 
            revision: revid, tree sha
308
 
        """
309
 
        def format(type, row):
310
 
            return (type, (row[0], row[1]))
311
 
        row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
312
 
        if row is not None:
313
 
            return format("commit", row)
314
 
        row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
315
 
        if row is not None:
316
 
            return format("blob", row)
317
 
        row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
318
 
        if row is not None:
319
 
            return format("tree", row)
320
 
        raise KeyError(sha)
321
 
 
322
 
    def revids(self):
323
 
        """List the revision ids known."""
324
 
        return (row for (row,) in self.db.execute("select revid from commits"))
325
 
 
326
 
    def sha1s(self):
327
 
        """List the SHA1s."""
328
 
        for table in ("blobs", "commits", "trees"):
329
 
            trace.note(table)
330
 
            for (row,) in self.db.execute("select sha1 from %s" % table):
331
 
                yield row
332
 
 
333
 
 
334
 
TDB_MAP_VERSION = 3
335
 
TDB_HASH_SIZE = 50000
336
 
 
337
 
 
338
 
class TdbGitShaMap(GitShaMap):
339
 
    """SHA Map that uses a TDB database.
340
 
 
341
 
    Entries:
342
 
 
343
 
    "git <sha1>" -> "<type> <type-data1> <type-data2>"
344
 
    "commit revid" -> "<sha1> <tree-id>"
345
 
    "tree fileid revid" -> "<sha1>"
346
 
    "blob fileid revid" -> "<sha1>"
347
 
    """
348
 
 
349
 
    def __init__(self, path=None):
350
 
        import tdb
351
 
        self.path = path
352
 
        if path is None:
353
 
            self.db = {}
354
 
        else:
355
 
            if not mapdbs().has_key(path):
356
 
                mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT,
357
 
                                          os.O_RDWR|os.O_CREAT)
358
 
            self.db = mapdbs()[path]
359
 
        try:
360
 
            if int(self.db["version"]) not in (2, 3):
361
 
                trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
362
 
                              self.db["version"], TDB_MAP_VERSION)
363
 
                self.db.clear()
364
 
        except KeyError:
365
 
            pass
366
 
        self.db["version"] = str(TDB_MAP_VERSION)
367
 
 
368
 
    def add_entries(self, revid, parent_revids, commit_sha, root_tree_sha, 
369
 
                    entries):
370
 
        """Add multiple new entries to the database.
371
 
        """
372
 
        self.db.transaction_start()
373
 
        try:
374
 
            self._add_entry(commit_sha, "commit", (revid, root_tree_sha))
375
 
            for (fileid, kind, hexsha, revision) in entries:
376
 
                self._add_entry(hexsha, kind, (fileid, revision))
377
 
        except:
378
 
            self.db.transaction_cancel()
379
 
            raise
380
 
        else:
381
 
            self.db.transaction_commit()
382
 
 
383
 
    def __repr__(self):
384
 
        return "%s(%r)" % (self.__class__.__name__, self.path)
385
 
 
386
 
    @classmethod
387
 
    def from_repository(cls, repository):
388
 
        try:
389
 
            transport = getattr(repository, "_transport", None)
390
 
            if transport is not None:
391
 
                return cls(os.path.join(transport.local_abspath("."), "git.tdb"))
392
 
        except bzrlib.errors.NotLocalUrl:
393
 
            pass
394
 
        return cls(os.path.join(get_cache_dir(), "remote.tdb"))
395
 
 
396
 
    def lookup_commit(self, revid):
397
 
        return sha_to_hex(self.db["commit\0" + revid][:20])
398
 
 
399
 
    def _add_entry(self, hexsha, type, type_data):
400
 
        """Add a new entry to the database.
401
 
        """
402
 
        if hexsha is None:
403
 
            sha = ""
404
 
        else:
405
 
            sha = hex_to_sha(hexsha)
406
 
            self.db["git\0" + sha] = "\0".join((type, type_data[0], type_data[1]))
407
 
        if type == "commit":
408
 
            self.db["commit\0" + type_data[0]] = "\0".join((sha, type_data[1]))
409
 
        elif type == "blob":
410
 
            self.db["\0".join(("blob", type_data[0], type_data[1]))] = sha
411
 
 
412
 
    def get_inventory_sha_map(self, revid):
413
 
 
414
 
        class TdbInventorySHAMap(InventorySHAMap):
415
 
 
416
 
            def __init__(self, db, revid):
417
 
                self.db = db
418
 
                self.revid = revid
419
 
 
420
 
            def lookup_blob(self, fileid, revision):
421
 
                return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
422
 
                
423
 
        return TdbInventorySHAMap(self.db, revid)
424
 
 
425
 
    def lookup_git_sha(self, sha):
426
 
        """Lookup a Git sha in the database.
427
 
 
428
 
        :param sha: Git object sha
429
 
        :return: (type, type_data) with type_data:
430
 
            revision: revid, tree sha
431
 
        """
432
 
        if len(sha) == 40:
433
 
            sha = hex_to_sha(sha)
434
 
        data = self.db["git\0" + sha].split("\0")
435
 
        return (data[0], (data[1], data[2]))
436
 
 
437
 
    def missing_revisions(self, revids):
438
 
        ret = set()
439
 
        for revid in revids:
440
 
            if self.db.get("commit\0" + revid) is None:
441
 
                ret.add(revid)
442
 
        return ret
443
 
 
444
 
    def revids(self):
445
 
        """List the revision ids known."""
446
 
        for key in self.db.iterkeys():
447
 
            if key.startswith("commit\0"):
448
 
                yield key[7:]
449
 
 
450
 
    def sha1s(self):
451
 
        """List the SHA1s."""
452
 
        for key in self.db.iterkeys():
453
 
            if key.startswith("git\0"):
454
 
                yield sha_to_hex(key[4:])
455
 
 
456
 
 
457
 
def from_repository(repository):
458
 
    try:
459
 
        return TdbGitShaMap.from_repository(repository)
460
 
    except ImportError:
461
 
        return SqliteGitShaMap.from_repository(repository)