/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

Fix some more tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
 
26
26
import bzrlib
27
27
from bzrlib import (
 
28
    registry,
28
29
    trace,
29
30
    )
 
31
from bzrlib.transport import (
 
32
    get_transport,
 
33
    )
30
34
 
31
35
 
32
36
def get_cache_dir():
42
46
    return ret
43
47
 
44
48
 
 
49
def get_remote_cache_transport():
 
50
    return get_transport(get_cache_dir())
 
51
 
 
52
 
45
53
def check_pysqlite_version(sqlite3):
46
54
    """Check that sqlite library is compatible.
47
55
 
48
56
    """
49
 
    if (sqlite3.sqlite_version_info[0] < 3 or 
50
 
            (sqlite3.sqlite_version_info[0] == 3 and 
 
57
    if (sqlite3.sqlite_version_info[0] < 3 or
 
58
            (sqlite3.sqlite_version_info[0] == 3 and
51
59
             sqlite3.sqlite_version_info[1] < 3)):
52
60
        trace.warning('Needs at least sqlite 3.3.x')
53
61
        raise bzrlib.errors.BzrError("incompatible sqlite library")
56
64
    try:
57
65
        import sqlite3
58
66
        check_pysqlite_version(sqlite3)
59
 
    except (ImportError, bzrlib.errors.BzrError), e: 
 
67
    except (ImportError, bzrlib.errors.BzrError), e:
60
68
        from pysqlite2 import dbapi2 as sqlite3
61
69
        check_pysqlite_version(sqlite3)
62
70
except:
78
86
class GitShaMap(object):
79
87
    """Git<->Bzr revision id mapping database."""
80
88
 
81
 
    def add_entry(self, sha, type, type_data):
82
 
        """Add a new entry to the database.
83
 
        """
84
 
        raise NotImplementedError(self.add_entry)
85
 
 
86
 
    def add_entries(self, entries):
87
 
        """Add multiple new entries to the database.
88
 
        """
89
 
        for e in entries:
90
 
            self.add_entry(*e)
91
 
 
92
 
    def lookup_tree(self, fileid, revid):
93
 
        """Lookup the SHA of a git tree."""
94
 
        raise NotImplementedError(self.lookup_tree)
95
 
 
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)
99
 
 
100
89
    def lookup_git_sha(self, sha):
101
90
        """Lookup a Git sha in the database.
102
 
 
103
91
        :param sha: Git object sha
104
92
        :return: (type, type_data) with type_data:
105
93
            revision: revid, tree sha
106
94
        """
107
95
        raise NotImplementedError(self.lookup_git_sha)
108
96
 
 
97
    def lookup_blob_id(self, file_id, revision):
 
98
        """Retrieve a Git blob SHA by file id.
 
99
 
 
100
        :param file_id: File id of the file/symlink
 
101
        :param revision: revision in which the file was last changed.
 
102
        """
 
103
        raise NotImplementedError(self.lookup_blob_id)
 
104
 
 
105
    def lookup_tree_id(self, file_id, revision):
 
106
        """Retrieve a Git tree SHA by file id.
 
107
        """
 
108
        raise NotImplementedError(self.lookup_tree_id)
 
109
 
109
110
    def revids(self):
110
111
        """List the revision ids known."""
111
112
        raise NotImplementedError(self.revids)
112
113
 
 
114
    def missing_revisions(self, revids):
 
115
        """Return set of all the revisions that are not present."""
 
116
        present_revids = set(self.revids())
 
117
        if not isinstance(revids, set):
 
118
            revids = set(revids)
 
119
        return revids - present_revids
 
120
 
113
121
    def sha1s(self):
114
122
        """List the SHA1s."""
115
123
        raise NotImplementedError(self.sha1s)
116
124
 
117
 
    def commit(self):
 
125
    def start_write_group(self):
 
126
        """Start writing changes."""
 
127
 
 
128
    def commit_write_group(self):
118
129
        """Commit any pending changes."""
119
130
 
 
131
    def abort_write_group(self):
 
132
        """Abort any pending changes."""
 
133
 
 
134
 
 
135
class ContentCache(object):
 
136
    """Object that can cache Git objects."""
 
137
 
 
138
    def __getitem__(self, sha):
 
139
        """Retrieve an item, by SHA."""
 
140
        raise NotImplementedError(self.__getitem__)
 
141
 
 
142
 
 
143
class BzrGitCacheFormat(object):
 
144
 
 
145
    def get_format_string(self):
 
146
        """Return a single-line unique format string for this cache format."""
 
147
        raise NotImplementedError(self.get_format_string)
 
148
 
 
149
    def open(self, transport):
 
150
        """Open this format on a transport."""
 
151
        raise NotImplementedError(self.open)
 
152
 
 
153
    def initialize(self, transport):
 
154
        transport.put_bytes('format', self.get_format_string())
 
155
 
 
156
    @classmethod
 
157
    def from_transport(self, transport):
 
158
        """Open a cache file present on a transport, or initialize one.
 
159
 
 
160
        :param transport: Transport to use
 
161
        :return: A BzrGitCache instance
 
162
        """
 
163
        try:
 
164
            format_name = transport.get_bytes('format')
 
165
            format = formats.get(format_name)
 
166
        except bzrlib.errors.NoSuchFile:
 
167
            format = formats.get('default')
 
168
            format.initialize(transport)
 
169
        return format.open(transport)
 
170
 
 
171
    @classmethod
 
172
    def from_repository(cls, repository):
 
173
        """Open a cache file for a repository.
 
174
 
 
175
        This will use the repository's transport to store the cache file, or
 
176
        use the users global cache directory if the repository has no 
 
177
        transport associated with it.
 
178
 
 
179
        :param repository: Repository to open the cache for
 
180
        :return: A `BzrGitCache`
 
181
        """
 
182
        repo_transport = getattr(repository, "_transport", None)
 
183
        if repo_transport is not None:
 
184
            # Even if we don't write to this repo, we should be able 
 
185
            # to update its cache.
 
186
            repo_transport = remove_readonly_transport_decorator(repo_transport)
 
187
            try:
 
188
                repo_transport.mkdir('git')
 
189
            except bzrlib.errors.FileExists:
 
190
                pass
 
191
            transport = repo_transport.clone('git')
 
192
        else:
 
193
            transport = get_remote_cache_transport()
 
194
        return cls.from_transport(transport)
 
195
 
 
196
 
 
197
class CacheUpdater(object):
 
198
 
 
199
    def add_object(self, obj, ie):
 
200
        raise NotImplementedError(self.add_object)
 
201
 
 
202
    def finish(self):
 
203
        raise NotImplementedError(self.finish)
 
204
 
 
205
 
 
206
class BzrGitCache(object):
 
207
    """Caching backend."""
 
208
 
 
209
    def __init__(self, idmap, content_cache, cache_updater_klass):
 
210
        self.idmap = idmap
 
211
        self.content_cache = content_cache
 
212
        self._cache_updater_klass = cache_updater_klass
 
213
 
 
214
    def get_updater(self, rev):
 
215
        return self._cache_updater_klass(self, rev)
 
216
 
 
217
 
 
218
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
 
219
 
 
220
 
 
221
class DictCacheUpdater(CacheUpdater):
 
222
 
 
223
    def __init__(self, cache, rev):
 
224
        self.cache = cache
 
225
        self.revid = rev.revision_id
 
226
        self.parent_revids = rev.parent_ids
 
227
        self._commit = None
 
228
        self._entries = []
 
229
 
 
230
    def add_object(self, obj, ie):
 
231
        if obj.type_name == "commit":
 
232
            self._commit = obj
 
233
            assert ie is None
 
234
            type_data = (self.revid, self._commit.tree)
 
235
            self.cache.idmap._by_revid[self.revid] = obj.id
 
236
        elif obj.type_name in ("blob", "tree"):
 
237
            if ie is not None:
 
238
                if obj.type_name == "blob":
 
239
                    revision = ie.revision
 
240
                else:
 
241
                    revision = self.revid
 
242
                type_data = (ie.file_id, revision)
 
243
                self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] =\
 
244
                    obj.id
 
245
        else:
 
246
            raise AssertionError
 
247
        self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
 
248
 
 
249
    def finish(self):
 
250
        if self._commit is None:
 
251
            raise AssertionError("No commit object added")
 
252
        return self._commit
 
253
 
120
254
 
121
255
class DictGitShaMap(GitShaMap):
122
256
 
123
257
    def __init__(self):
124
 
        self.dict = {}
 
258
        self._by_sha = {}
 
259
        self._by_fileid = {}
 
260
        self._by_revid = {}
125
261
 
126
 
    def add_entry(self, sha, type, type_data):
127
 
        self.dict[sha] = (type, type_data)
 
262
    def lookup_blob_id(self, fileid, revision):
 
263
        return self._by_fileid[revision][fileid]
128
264
 
129
265
    def lookup_git_sha(self, sha):
130
 
        return self.dict[sha]
131
 
 
132
 
    def lookup_tree(self, fileid, revid):
133
 
        for k, v in self.dict.iteritems():
134
 
            if v == ("tree", (fileid, revid)):
135
 
                return k
136
 
        raise KeyError((fileid, revid))
137
 
 
138
 
    def lookup_blob(self, fileid, revid):
139
 
        for k, v in self.dict.iteritems():
140
 
            if v == ("blob", (fileid, revid)):
141
 
                return k
142
 
        raise KeyError((fileid, revid))
 
266
        return self._by_sha[sha]
 
267
 
 
268
    def lookup_tree_id(self, fileid, revision):
 
269
        return self._by_fileid[revision][fileid]
 
270
 
 
271
    def lookup_commit(self, revid):
 
272
        return self._by_revid[revid]
143
273
 
144
274
    def revids(self):
145
 
        for key, (type, type_data) in self.dict.iteritems():
 
275
        for key, (type, type_data) in self._by_sha.iteritems():
146
276
            if type == "commit":
147
277
                yield type_data[0]
148
278
 
149
279
    def sha1s(self):
150
 
        return self.dict.iterkeys()
 
280
        return self._by_sha.iterkeys()
 
281
 
 
282
 
 
283
class SqliteCacheUpdater(CacheUpdater):
 
284
 
 
285
    def __init__(self, cache, rev):
 
286
        self.cache = cache
 
287
        self.db = self.cache.idmap.db
 
288
        self.revid = rev.revision_id
 
289
        self._commit = None
 
290
        self._trees = []
 
291
        self._blobs = []
 
292
 
 
293
    def add_object(self, obj, ie):
 
294
        if obj.type_name == "commit":
 
295
            self._commit = obj
 
296
            assert ie is None
 
297
        elif obj.type_name == "tree":
 
298
            if ie is not None:
 
299
                self._trees.append((obj.id, ie.file_id, self.revid))
 
300
        elif obj.type_name == "blob":
 
301
            if ie is not None:
 
302
                self._blobs.append((obj.id, ie.file_id, ie.revision))
 
303
        else:
 
304
            raise AssertionError
 
305
 
 
306
    def finish(self):
 
307
        if self._commit is None:
 
308
            raise AssertionError("No commit object added")
 
309
        self.db.executemany(
 
310
            "replace into trees (sha1, fileid, revid) values (?, ?, ?)",
 
311
            self._trees)
 
312
        self.db.executemany(
 
313
            "replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
 
314
            self._blobs)
 
315
        self.db.execute(
 
316
            "replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
 
317
            (self._commit.id, self.revid, self._commit.tree))
 
318
        return self._commit
 
319
 
 
320
 
 
321
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
 
322
 
 
323
 
 
324
class SqliteGitCacheFormat(BzrGitCacheFormat):
 
325
 
 
326
    def get_format_string(self):
 
327
        return 'bzr-git sha map version 1 using sqlite\n'
 
328
 
 
329
    def open(self, transport):
 
330
        try:
 
331
            basepath = transport.local_abspath(".")
 
332
        except bzrlib.errors.NotLocalUrl:
 
333
            basepath = get_cache_dir()
 
334
        return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
151
335
 
152
336
 
153
337
class SqliteGitShaMap(GitShaMap):
159
343
        else:
160
344
            if not mapdbs().has_key(path):
161
345
                mapdbs()[path] = sqlite3.connect(path)
162
 
            self.db = mapdbs()[path]    
 
346
            self.db = mapdbs()[path]
 
347
        self.db.text_factory = str
163
348
        self.db.executescript("""
164
 
        create table if not exists commits(sha1 text, revid text, tree_sha text);
 
349
        create table if not exists commits(
 
350
            sha1 text not null check(length(sha1) == 40),
 
351
            revid text not null,
 
352
            tree_sha text not null check(length(tree_sha) == 40)
 
353
        );
165
354
        create index if not exists commit_sha1 on commits(sha1);
166
355
        create unique index if not exists commit_revid on commits(revid);
167
 
        create table if not exists blobs(sha1 text, fileid text, revid text);
 
356
        create table if not exists blobs(
 
357
            sha1 text not null check(length(sha1) == 40),
 
358
            fileid text not null,
 
359
            revid text not null
 
360
        );
168
361
        create index if not exists blobs_sha1 on blobs(sha1);
169
362
        create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
170
 
        create table if not exists trees(sha1 text, fileid text, revid text);
171
 
        create index if not exists trees_sha1 on trees(sha1);
 
363
        create table if not exists trees(
 
364
            sha1 text unique not null check(length(sha1) == 40),
 
365
            fileid text not null,
 
366
            revid text not null
 
367
        );
 
368
        create unique index if not exists trees_sha1 on trees(sha1);
172
369
        create unique index if not exists trees_fileid_revid on trees(fileid, revid);
173
370
""")
174
371
 
175
 
    @classmethod
176
 
    def from_repository(cls, repository):
177
 
        try:
178
 
            transport = getattr(repository, "_transport", None)
179
 
            if transport is not None:
180
 
                return cls(os.path.join(transport.local_abspath("."), "git.db"))
181
 
        except bzrlib.errors.NotLocalUrl:
182
 
            pass
183
 
        return cls(os.path.join(get_cache_dir(), "remote.db"))
184
 
 
 
372
    def __repr__(self):
 
373
        return "%s(%r)" % (self.__class__.__name__, self.path)
 
374
    
185
375
    def lookup_commit(self, revid):
186
376
        row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
187
377
        if row is not None:
188
 
            return row[0].encode("utf-8")
 
378
            return row[0]
189
379
        raise KeyError
190
380
 
191
 
    def commit(self):
 
381
    def commit_write_group(self):
192
382
        self.db.commit()
193
383
 
194
 
    def add_entries(self, entries):
195
 
        trees = []
196
 
        blobs = []
197
 
        for sha, type, type_data in entries:
198
 
            assert isinstance(type_data[0], str)
199
 
            assert isinstance(type_data[1], str)
200
 
            entry = (sha.decode("utf-8"), type_data[0].decode("utf-8"), 
201
 
                     type_data[1].decode("utf-8"))
202
 
            if type == "tree":
203
 
                trees.append(entry)
204
 
            elif type == "blob":
205
 
                blobs.append(entry)
206
 
            else:
207
 
                raise AssertionError
208
 
        if trees:
209
 
            self.db.executemany("replace into trees (sha1, fileid, revid) values (?, ?, ?)", trees)
210
 
        if blobs:
211
 
            self.db.executemany("replace into blobs (sha1, fileid, revid) values (?, ?, ?)", blobs)
212
 
 
213
 
 
214
 
    def add_entry(self, sha, type, type_data):
215
 
        """Add a new entry to the database.
216
 
        """
217
 
        assert isinstance(type_data, tuple)
218
 
        assert isinstance(sha, str), "type was %r" % sha
219
 
        if type == "commit":
220
 
            self.db.execute("replace into commits (sha1, revid, tree_sha) values (?, ?, ?)", (sha, type_data[0], type_data[1]))
221
 
        elif type in ("blob", "tree"):
222
 
            self.db.execute("replace into %ss (sha1, fileid, revid) values (?, ?, ?)" % type, (sha, type_data[0], type_data[1]))
223
 
        else:
224
 
            raise AssertionError("Unknown type %s" % type)
225
 
 
226
 
    def lookup_tree(self, fileid, revid):
227
 
        row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid,revid)).fetchone()
228
 
        if row is None:
229
 
            raise KeyError((fileid, revid))
230
 
        return row[0].encode("utf-8")
231
 
 
232
 
    def lookup_blob(self, fileid, revid):
233
 
        row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revid)).fetchone()
234
 
        if row is None:
235
 
            raise KeyError((fileid, revid))
236
 
        return row[0].encode("utf-8")
 
384
    def lookup_blob_id(self, fileid, revision):
 
385
        row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
 
386
        if row is not None:
 
387
            return row[0]
 
388
        raise KeyError(fileid)
 
389
 
 
390
    def lookup_tree_id(self, fileid, revision):
 
391
        row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
 
392
        if row is not None:
 
393
            return row[0]
 
394
        raise KeyError(fileid)
237
395
 
238
396
    def lookup_git_sha(self, sha):
239
397
        """Lookup a Git sha in the database.
242
400
        :return: (type, type_data) with type_data:
243
401
            revision: revid, tree sha
244
402
        """
245
 
        def format(type, row):
246
 
            return (type, (row[0].encode("utf-8"), row[1].encode("utf-8")))
247
403
        row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
248
404
        if row is not None:
249
 
            return format("commit", row)
 
405
            return ("commit", row)
250
406
        row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
251
407
        if row is not None:
252
 
            return format("blob", row)
 
408
            return ("blob", row)
253
409
        row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
254
410
        if row is not None:
255
 
            return format("tree", row)
 
411
            return ("tree", row)
256
412
        raise KeyError(sha)
257
413
 
258
414
    def revids(self):
259
415
        """List the revision ids known."""
260
 
        for row in self.db.execute("select revid from commits").fetchall():
261
 
            yield row[0].encode("utf-8")
 
416
        return (row for (row,) in self.db.execute("select revid from commits"))
262
417
 
263
418
    def sha1s(self):
264
419
        """List the SHA1s."""
265
420
        for table in ("blobs", "commits", "trees"):
266
 
            for row in self.db.execute("select sha1 from %s" % table).fetchall():
267
 
                yield row[0].encode("utf-8")
268
 
 
269
 
 
270
 
TDB_MAP_VERSION = 2
271
 
TDB_HASH_SIZE = 50000
 
421
            for (sha,) in self.db.execute("select sha1 from %s" % table):
 
422
                yield sha
 
423
 
 
424
 
 
425
class TdbCacheUpdater(CacheUpdater):
 
426
 
 
427
    def __init__(self, cache, rev):
 
428
        self.cache = cache
 
429
        self.db = cache.idmap.db
 
430
        self.revid = rev.revision_id
 
431
        self.parent_revids = rev.parent_ids
 
432
        self._commit = None
 
433
        self._entries = []
 
434
 
 
435
    def add_object(self, obj, ie):
 
436
        sha = obj.sha().digest()
 
437
        if obj.type_name == "commit":
 
438
            self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
 
439
            type_data = (self.revid, obj.tree)
 
440
            self._commit = obj
 
441
            assert ie is None
 
442
        elif obj.type_name == "blob":
 
443
            if ie is None:
 
444
                return
 
445
            self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
 
446
            type_data = (ie.file_id, ie.revision)
 
447
        elif obj.type_name == "tree":
 
448
            if ie is None:
 
449
                return
 
450
            type_data = (ie.file_id, self.revid)
 
451
        else:
 
452
            raise AssertionError
 
453
        self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
 
454
 
 
455
    def finish(self):
 
456
        if self._commit is None:
 
457
            raise AssertionError("No commit object added")
 
458
        return self._commit
 
459
 
 
460
 
 
461
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
 
462
 
 
463
class TdbGitCacheFormat(BzrGitCacheFormat):
 
464
 
 
465
    def get_format_string(self):
 
466
        return 'bzr-git sha map version 3 using tdb\n'
 
467
 
 
468
    def open(self, transport):
 
469
        try:
 
470
            basepath = transport.local_abspath(".")
 
471
        except bzrlib.errors.NotLocalUrl:
 
472
            basepath = get_cache_dir()
 
473
        try:
 
474
            return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
 
475
        except ImportError:
 
476
            raise ImportError(
 
477
                "Unable to open existing bzr-git cache because 'tdb' is not "
 
478
                "installed.")
272
479
 
273
480
 
274
481
class TdbGitShaMap(GitShaMap):
282
489
    "blob fileid revid" -> "<sha1>"
283
490
    """
284
491
 
 
492
    TDB_MAP_VERSION = 3
 
493
    TDB_HASH_SIZE = 50000
 
494
 
285
495
    def __init__(self, path=None):
286
496
        import tdb
287
497
        self.path = path
289
499
            self.db = {}
290
500
        else:
291
501
            if not mapdbs().has_key(path):
292
 
                mapdbs()[path] = tdb.Tdb(path, TDB_HASH_SIZE, tdb.DEFAULT, 
 
502
                mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
293
503
                                          os.O_RDWR|os.O_CREAT)
294
 
            self.db = mapdbs()[path]    
295
 
        if not "version" in self.db:
296
 
            self.db["version"] = str(TDB_MAP_VERSION)
297
 
        else:
298
 
            if int(self.db["version"]) != TDB_MAP_VERSION:
 
504
            self.db = mapdbs()[path]
 
505
        try:
 
506
            if int(self.db["version"]) not in (2, 3):
299
507
                trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
300
 
                              self.db["version"], TDB_MAP_VERSION)
 
508
                              self.db["version"], self.TDB_MAP_VERSION)
301
509
                self.db.clear()
302
 
            self.db["version"] = str(TDB_MAP_VERSION)
303
 
 
304
 
    @classmethod
305
 
    def from_repository(cls, repository):
306
 
        try:
307
 
            transport = getattr(repository, "_transport", None)
308
 
            if transport is not None:
309
 
                return cls(os.path.join(transport.local_abspath("."), "git.tdb"))
310
 
        except bzrlib.errors.NotLocalUrl:
 
510
        except KeyError:
311
511
            pass
312
 
        return cls(os.path.join(get_cache_dir(), "remote.tdb"))
 
512
        self.db["version"] = str(self.TDB_MAP_VERSION)
 
513
 
 
514
    def start_write_group(self):
 
515
        """Start writing changes."""
 
516
        self.db.transaction_start()
 
517
 
 
518
    def commit_write_group(self):
 
519
        """Commit any pending changes."""
 
520
        self.db.transaction_commit()
 
521
 
 
522
    def abort_write_group(self):
 
523
        """Abort any pending changes."""
 
524
        self.db.transaction_cancel()
 
525
 
 
526
    def __repr__(self):
 
527
        return "%s(%r)" % (self.__class__.__name__, self.path)
313
528
 
314
529
    def lookup_commit(self, revid):
315
530
        return sha_to_hex(self.db["commit\0" + revid][:20])
316
531
 
317
 
    def commit(self):
318
 
        pass
319
 
 
320
 
    def add_entry(self, hexsha, type, type_data):
321
 
        """Add a new entry to the database.
322
 
        """
323
 
        if hexsha is None:
324
 
            sha = ""
325
 
        else:
326
 
            sha = hex_to_sha(hexsha)
327
 
            self.db["git\0" + sha] = "\0".join((type, type_data[0], type_data[1]))
328
 
        if type == "commit":
329
 
            self.db["commit\0" + type_data[0]] = "\0".join((sha, type_data[1]))
330
 
        else:
331
 
            self.db["\0".join((type, type_data[0], type_data[1]))] = sha
332
 
 
333
 
    def lookup_tree(self, fileid, revid):
334
 
        sha = self.db["\0".join(("tree", fileid, revid))]
335
 
        if sha == "":
336
 
            return None
337
 
        else:
338
 
            return sha_to_hex(sha)
339
 
 
340
 
    def lookup_blob(self, fileid, revid):
341
 
        return sha_to_hex(self.db["\0".join(("blob", fileid, revid))])
342
 
 
 
532
    def lookup_blob_id(self, fileid, revision):
 
533
        return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
 
534
                
343
535
    def lookup_git_sha(self, sha):
344
536
        """Lookup a Git sha in the database.
345
537
 
352
544
        data = self.db["git\0" + sha].split("\0")
353
545
        return (data[0], (data[1], data[2]))
354
546
 
 
547
    def missing_revisions(self, revids):
 
548
        ret = set()
 
549
        for revid in revids:
 
550
            if self.db.get("commit\0" + revid) is None:
 
551
                ret.add(revid)
 
552
        return ret
 
553
 
355
554
    def revids(self):
356
555
        """List the revision ids known."""
357
556
        for key in self.db.iterkeys():
363
562
        for key in self.db.iterkeys():
364
563
            if key.startswith("git\0"):
365
564
                yield sha_to_hex(key[4:])
 
565
 
 
566
 
 
567
formats = registry.Registry()
 
568
formats.register(TdbGitCacheFormat().get_format_string(),
 
569
    TdbGitCacheFormat())
 
570
formats.register(SqliteGitCacheFormat().get_format_string(),
 
571
    SqliteGitCacheFormat())
 
572
try:
 
573
    import tdb
 
574
except ImportError:
 
575
    formats.register('default', SqliteGitCacheFormat())
 
576
else:
 
577
    formats.register('default', TdbGitCacheFormat())
 
578
 
 
579
 
 
580
def migrate_ancient_formats(repo_transport):
 
581
    # Prefer migrating git.db over git.tdb, since the latter may not 
 
582
    # be openable on some platforms.
 
583
    if repo_transport.has("git.db"):
 
584
        SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
 
585
        repo_transport.rename("git.db", "git/idmap.db")
 
586
    elif repo_transport.has("git.tdb"):
 
587
        TdbGitCacheFormat().initialize(repo_transport.clone("git"))
 
588
        repo_transport.rename("git.tdb", "git/idmap.tdb")
 
589
 
 
590
 
 
591
def remove_readonly_transport_decorator(transport):
 
592
    if transport.is_readonly():
 
593
        return transport._decorated
 
594
    return transport
 
595
 
 
596
 
 
597
def from_repository(repository):
 
598
    """Open a cache file for a repository.
 
599
 
 
600
    If the repository is remote and there is no transport available from it
 
601
    this will use a local file in the users cache directory
 
602
    (typically ~/.cache/bazaar/git/)
 
603
 
 
604
    :param repository: A repository object
 
605
    """
 
606
    repo_transport = getattr(repository, "_transport", None)
 
607
    if repo_transport is not None:
 
608
        # Migrate older cache formats
 
609
        repo_transport = remove_readonly_transport_decorator(repo_transport)
 
610
        try:
 
611
            repo_transport.mkdir("git")
 
612
        except bzrlib.errors.FileExists:
 
613
            pass
 
614
        else:
 
615
            migrate_ancient_formats(repo_transport)
 
616
    return BzrGitCacheFormat.from_repository(repository)