/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 breezy/git/cache.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-07 02:14:30 UTC
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200207021430-m49iq3x4x8xlib6x
Drop python2 support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
 
1
# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Map from Git sha's to Bazaar objects."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
from dulwich.objects import (
20
22
    sha_to_hex,
21
23
    hex_to_sha,
27
29
    ShaFile,
28
30
    )
29
31
 
30
 
import bzrlib
31
 
from bzrlib import (
32
 
    btree_index as _mod_btree_index,
33
 
    index as _mod_index,
 
32
from .. import (
 
33
    bedding,
 
34
    errors as bzr_errors,
34
35
    osutils,
35
36
    registry,
36
37
    trace,
 
38
    )
 
39
from ..bzr import (
 
40
    btree_index as _mod_btree_index,
 
41
    index as _mod_index,
37
42
    versionedfile,
38
43
    )
39
 
from bzrlib.transport import (
40
 
    get_transport,
 
44
from ..transport import (
 
45
    get_transport_from_path,
41
46
    )
42
47
 
43
48
 
44
49
def get_cache_dir():
45
 
    try:
46
 
        from xdg.BaseDirectory import xdg_cache_home
47
 
    except ImportError:
48
 
        from bzrlib.config import config_dir
49
 
        ret = os.path.join(config_dir(), "git")
 
50
    path = os.path.join(bedding.cache_dir(), "git")
 
51
    if not os.path.isdir(path):
 
52
        os.mkdir(path)
 
53
    return path
 
54
 
 
55
 
 
56
def get_remote_cache_transport(repository):
 
57
    """Retrieve the transport to use when accessing (unwritable) remote
 
58
    repositories.
 
59
    """
 
60
    uuid = getattr(repository, "uuid", None)
 
61
    if uuid is None:
 
62
        path = get_cache_dir()
50
63
    else:
51
 
        ret = os.path.join(xdg_cache_home, "bazaar", "git")
52
 
    if not os.path.isdir(ret):
53
 
        os.makedirs(ret)
54
 
    return ret
55
 
 
56
 
 
57
 
def get_remote_cache_transport():
58
 
    """Retrieve the transport to use when accessing (unwritable) remote 
59
 
    repositories.
60
 
    """
61
 
    return get_transport(get_cache_dir())
 
64
        path = os.path.join(get_cache_dir(), uuid)
 
65
        if not os.path.isdir(path):
 
66
            os.mkdir(path)
 
67
    return get_transport_from_path(path)
62
68
 
63
69
 
64
70
def check_pysqlite_version(sqlite3):
65
71
    """Check that sqlite library is compatible.
66
72
 
67
73
    """
68
 
    if (sqlite3.sqlite_version_info[0] < 3 or
69
 
            (sqlite3.sqlite_version_info[0] == 3 and
70
 
             sqlite3.sqlite_version_info[1] < 3)):
 
74
    if (sqlite3.sqlite_version_info[0] < 3
 
75
            or (sqlite3.sqlite_version_info[0] == 3 and
 
76
                sqlite3.sqlite_version_info[1] < 3)):
71
77
        trace.warning('Needs at least sqlite 3.3.x')
72
 
        raise bzrlib.errors.BzrError("incompatible sqlite library")
 
78
        raise bzr_errors.BzrError("incompatible sqlite library")
 
79
 
73
80
 
74
81
try:
75
82
    try:
76
83
        import sqlite3
77
84
        check_pysqlite_version(sqlite3)
78
 
    except (ImportError, bzrlib.errors.BzrError), e:
 
85
    except (ImportError, bzr_errors.BzrError):
79
86
        from pysqlite2 import dbapi2 as sqlite3
80
87
        check_pysqlite_version(sqlite3)
81
 
except:
 
88
except BaseException:
82
89
    trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
83
 
            'module')
84
 
    raise bzrlib.errors.BzrError("missing sqlite library")
 
90
                  'module')
 
91
    raise bzr_errors.BzrError("missing sqlite library")
85
92
 
86
93
 
87
94
_mapdbs = threading.local()
 
95
 
 
96
 
88
97
def mapdbs():
89
98
    """Get a cache for this thread's db connections."""
90
99
    try:
100
109
    def lookup_git_sha(self, sha):
101
110
        """Lookup a Git sha in the database.
102
111
        :param sha: Git object sha
103
 
        :return: (type, type_data) with type_data:
 
112
        :return: list with (type, type_data) tuples with type_data:
104
113
            commit: revid, tree_sha, verifiers
105
114
            blob: fileid, revid
106
115
            tree: fileid, revid
120
129
        """
121
130
        raise NotImplementedError(self.lookup_tree_id)
122
131
 
 
132
    def lookup_commit(self, revid):
 
133
        """Retrieve a Git commit SHA by Bazaar revision id.
 
134
        """
 
135
        raise NotImplementedError(self.lookup_commit)
 
136
 
123
137
    def revids(self):
124
138
        """List the revision ids known."""
125
139
        raise NotImplementedError(self.revids)
187
201
        try:
188
202
            format_name = transport.get_bytes('format')
189
203
            format = formats.get(format_name)
190
 
        except bzrlib.errors.NoSuchFile:
 
204
        except bzr_errors.NoSuchFile:
191
205
            format = formats.get('default')
192
206
            format.initialize(transport)
193
207
        return format.open(transport)
197
211
        """Open a cache file for a repository.
198
212
 
199
213
        This will use the repository's transport to store the cache file, or
200
 
        use the users global cache directory if the repository has no 
 
214
        use the users global cache directory if the repository has no
201
215
        transport associated with it.
202
216
 
203
217
        :param repository: Repository to open the cache for
204
218
        :return: A `BzrGitCache`
205
219
        """
 
220
        from ..transport.local import LocalTransport
206
221
        repo_transport = getattr(repository, "_transport", None)
207
 
        if repo_transport is not None:
208
 
            # Even if we don't write to this repo, we should be able 
 
222
        if (repo_transport is not None
 
223
                and isinstance(repo_transport, LocalTransport)):
 
224
            # Even if we don't write to this repo, we should be able
209
225
            # to update its cache.
210
 
            repo_transport = remove_readonly_transport_decorator(repo_transport)
211
226
            try:
212
 
                repo_transport.mkdir('git')
213
 
            except bzrlib.errors.FileExists:
214
 
                pass
215
 
            transport = repo_transport.clone('git')
 
227
                repo_transport = remove_readonly_transport_decorator(
 
228
                    repo_transport)
 
229
            except bzr_errors.ReadOnlyError:
 
230
                transport = None
 
231
            else:
 
232
                try:
 
233
                    repo_transport.mkdir('git')
 
234
                except bzr_errors.FileExists:
 
235
                    pass
 
236
                transport = repo_transport.clone('git')
216
237
        else:
217
 
            transport = get_remote_cache_transport()
 
238
            transport = None
 
239
        if transport is None:
 
240
            transport = get_remote_cache_transport(repository)
218
241
        return cls.from_transport(transport)
219
242
 
220
243
 
221
244
class CacheUpdater(object):
222
245
    """Base class for objects that can update a bzr-git cache."""
223
246
 
224
 
    def add_object(self, obj, ie, path):
 
247
    def add_object(self, obj, bzr_key_data, path):
225
248
        """Add an object.
226
249
 
227
250
        :param obj: Object type ("commit", "blob" or "tree")
228
 
        :param ie: Inventory entry (for blob/tree) or testament_sha in case
 
251
        :param bzr_key_data: bzr key store data or testament_sha in case
229
252
            of commit
230
253
        :param path: Path of the object (optional)
231
254
        """
238
261
class BzrGitCache(object):
239
262
    """Caching backend."""
240
263
 
241
 
    def __init__(self, idmap, content_cache, cache_updater_klass):
 
264
    def __init__(self, idmap, cache_updater_klass):
242
265
        self.idmap = idmap
243
 
        self.content_cache = content_cache
244
266
        self._cache_updater_klass = cache_updater_klass
245
267
 
246
268
    def get_updater(self, rev):
247
 
        """Update an object that implements the CacheUpdater interface for 
 
269
        """Update an object that implements the CacheUpdater interface for
248
270
        updating this cache.
249
271
        """
250
272
        return self._cache_updater_klass(self, rev)
251
273
 
252
274
 
253
 
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), None, DictCacheUpdater)
 
275
def DictBzrGitCache():
 
276
    return BzrGitCache(DictGitShaMap(), DictCacheUpdater)
254
277
 
255
278
 
256
279
class DictCacheUpdater(CacheUpdater):
263
286
        self._commit = None
264
287
        self._entries = []
265
288
 
266
 
    def add_object(self, obj, ie, path):
267
 
        if obj.type_name == "commit":
 
289
    def add_object(self, obj, bzr_key_data, path):
 
290
        if isinstance(obj, tuple):
 
291
            (type_name, hexsha) = obj
 
292
        else:
 
293
            type_name = obj.type_name.decode('ascii')
 
294
            hexsha = obj.id
 
295
        if not isinstance(hexsha, bytes):
 
296
            raise TypeError(hexsha)
 
297
        if type_name == "commit":
268
298
            self._commit = obj
269
 
            assert type(ie) is dict
270
 
            type_data = (self.revid, self._commit.tree, ie)
271
 
            self.cache.idmap._by_revid[self.revid] = obj.id
272
 
        elif obj.type_name in ("blob", "tree"):
273
 
            if ie is not None:
274
 
                if obj.type_name == "blob":
275
 
                    revision = ie.revision
276
 
                else:
277
 
                    revision = self.revid
278
 
                type_data = (ie.file_id, revision)
279
 
                self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
 
299
            if type(bzr_key_data) is not dict:
 
300
                raise TypeError(bzr_key_data)
 
301
            key = self.revid
 
302
            type_data = (self.revid, self._commit.tree, bzr_key_data)
 
303
            self.cache.idmap._by_revid[self.revid] = hexsha
 
304
        elif type_name in ("blob", "tree"):
 
305
            if bzr_key_data is not None:
 
306
                key = type_data = bzr_key_data
 
307
                self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
 
308
                    type_data[0]] = hexsha
280
309
        else:
281
310
            raise AssertionError
282
 
        self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
 
311
        entry = (type_name, type_data)
 
312
        self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
283
313
 
284
314
    def finish(self):
285
315
        if self._commit is None:
299
329
        return self._by_fileid[revision][fileid]
300
330
 
301
331
    def lookup_git_sha(self, sha):
302
 
        return self._by_sha[sha]
 
332
        if not isinstance(sha, bytes):
 
333
            raise TypeError(sha)
 
334
        for entry in self._by_sha[sha].values():
 
335
            yield entry
303
336
 
304
337
    def lookup_tree_id(self, fileid, revision):
305
338
        return self._by_fileid[revision][fileid]
308
341
        return self._by_revid[revid]
309
342
 
310
343
    def revids(self):
311
 
        for key, (type, type_data) in self._by_sha.iteritems():
312
 
            if type == "commit":
313
 
                yield type_data[0]
 
344
        for key, entries in self._by_sha.items():
 
345
            for (type, type_data) in entries.values():
 
346
                if type == "commit":
 
347
                    yield type_data[0]
314
348
 
315
349
    def sha1s(self):
316
 
        return self._by_sha.iterkeys()
 
350
        return self._by_sha.keys()
317
351
 
318
352
 
319
353
class SqliteCacheUpdater(CacheUpdater):
326
360
        self._trees = []
327
361
        self._blobs = []
328
362
 
329
 
    def add_object(self, obj, ie, path):
330
 
        if obj.type_name == "commit":
 
363
    def add_object(self, obj, bzr_key_data, path):
 
364
        if isinstance(obj, tuple):
 
365
            (type_name, hexsha) = obj
 
366
        else:
 
367
            type_name = obj.type_name.decode('ascii')
 
368
            hexsha = obj.id
 
369
        if not isinstance(hexsha, bytes):
 
370
            raise TypeError(hexsha)
 
371
        if type_name == "commit":
331
372
            self._commit = obj
332
 
            self._testament3_sha1 = ie["testament3-sha1"]
333
 
            assert type(ie) is dict
334
 
        elif obj.type_name == "tree":
335
 
            if ie is not None:
336
 
                self._trees.append((obj.id, ie.file_id, self.revid))
337
 
        elif obj.type_name == "blob":
338
 
            if ie is not None:
339
 
                self._blobs.append((obj.id, ie.file_id, ie.revision))
 
373
            if type(bzr_key_data) is not dict:
 
374
                raise TypeError(bzr_key_data)
 
375
            self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
 
376
        elif type_name == "tree":
 
377
            if bzr_key_data is not None:
 
378
                self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
 
379
        elif type_name == "blob":
 
380
            if bzr_key_data is not None:
 
381
                self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
340
382
        else:
341
383
            raise AssertionError
342
384
 
350
392
            "replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
351
393
            self._blobs)
352
394
        self.db.execute(
353
 
            "replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
354
 
            (self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
 
395
            "replace into commits (sha1, revid, tree_sha, testament3_sha1) "
 
396
            "values (?, ?, ?, ?)",
 
397
            (self._commit.id, self.revid, self._commit.tree,
 
398
                self._testament3_sha1))
355
399
        return self._commit
356
400
 
357
401
 
358
 
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
 
402
def SqliteBzrGitCache(p):
 
403
    return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
359
404
 
360
405
 
361
406
class SqliteGitCacheFormat(BzrGitCacheFormat):
362
407
 
363
408
    def get_format_string(self):
364
 
        return 'bzr-git sha map version 1 using sqlite\n'
 
409
        return b'bzr-git sha map version 1 using sqlite\n'
365
410
 
366
411
    def open(self, transport):
367
412
        try:
368
413
            basepath = transport.local_abspath(".")
369
 
        except bzrlib.errors.NotLocalUrl:
 
414
        except bzr_errors.NotLocalUrl:
370
415
            basepath = get_cache_dir()
371
416
        return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
372
417
 
379
424
        if path is None:
380
425
            self.db = sqlite3.connect(":memory:")
381
426
        else:
382
 
            if not mapdbs().has_key(path):
 
427
            if path not in mapdbs():
383
428
                mapdbs()[path] = sqlite3.connect(path)
384
429
            self.db = mapdbs()[path]
385
430
        self.db.text_factory = str
397
442
            revid text not null
398
443
        );
399
444
        create index if not exists blobs_sha1 on blobs(sha1);
400
 
        create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
 
445
        create unique index if not exists blobs_fileid_revid on blobs(
 
446
            fileid, revid);
401
447
        create table if not exists trees(
402
448
            sha1 text unique not null check(length(sha1) == 40),
403
449
            fileid text not null,
404
450
            revid text not null
405
451
        );
406
452
        create unique index if not exists trees_sha1 on trees(sha1);
407
 
        create unique index if not exists trees_fileid_revid on trees(fileid, revid);
 
453
        create unique index if not exists trees_fileid_revid on trees(
 
454
            fileid, revid);
408
455
""")
409
456
        try:
410
457
            self.db.executescript(
411
458
                "ALTER TABLE commits ADD testament3_sha1 TEXT;")
412
459
        except sqlite3.OperationalError:
413
 
            pass # Column already exists.
 
460
            pass  # Column already exists.
414
461
 
415
462
    def __repr__(self):
416
463
        return "%s(%r)" % (self.__class__.__name__, self.path)
417
464
 
418
465
    def lookup_commit(self, revid):
419
 
        cursor = self.db.execute("select sha1 from commits where revid = ?", 
420
 
            (revid,))
 
466
        cursor = self.db.execute("select sha1 from commits where revid = ?",
 
467
                                 (revid,))
421
468
        row = cursor.fetchone()
422
469
        if row is not None:
423
470
            return row[0]
427
474
        self.db.commit()
428
475
 
429
476
    def lookup_blob_id(self, fileid, revision):
430
 
        row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
 
477
        row = self.db.execute(
 
478
            "select sha1 from blobs where fileid = ? and revid = ?",
 
479
            (fileid, revision)).fetchone()
431
480
        if row is not None:
432
481
            return row[0]
433
482
        raise KeyError(fileid)
434
483
 
435
484
    def lookup_tree_id(self, fileid, revision):
436
 
        row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
 
485
        row = self.db.execute(
 
486
            "select sha1 from trees where fileid = ? and revid = ?",
 
487
            (fileid, revision)).fetchone()
437
488
        if row is not None:
438
489
            return row[0]
439
490
        raise KeyError(fileid)
447
498
            tree: fileid, revid
448
499
            blob: fileid, revid
449
500
        """
450
 
        row = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,)).fetchone()
451
 
        if row is not None:
452
 
            return ("commit", (row[0], row[1], {"testament3-sha1": row[2]}))
453
 
        row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
454
 
        if row is not None:
455
 
            return ("blob", row)
456
 
        row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
457
 
        if row is not None:
458
 
            return ("tree", row)
459
 
        raise KeyError(sha)
 
501
        found = False
 
502
        cursor = self.db.execute(
 
503
            "select revid, tree_sha, testament3_sha1 from commits where "
 
504
            "sha1 = ?", (sha,))
 
505
        for row in cursor.fetchall():
 
506
            found = True
 
507
            if row[2] is not None:
 
508
                verifiers = {"testament3-sha1": row[2]}
 
509
            else:
 
510
                verifiers = {}
 
511
            yield ("commit", (row[0], row[1], verifiers))
 
512
        cursor = self.db.execute(
 
513
            "select fileid, revid from blobs where sha1 = ?", (sha,))
 
514
        for row in cursor.fetchall():
 
515
            found = True
 
516
            yield ("blob", row)
 
517
        cursor = self.db.execute(
 
518
            "select fileid, revid from trees where sha1 = ?", (sha,))
 
519
        for row in cursor.fetchall():
 
520
            found = True
 
521
            yield ("tree", row)
 
522
        if not found:
 
523
            raise KeyError(sha)
460
524
 
461
525
    def revids(self):
462
526
        """List the revision ids known."""
466
530
        """List the SHA1s."""
467
531
        for table in ("blobs", "commits", "trees"):
468
532
            for (sha,) in self.db.execute("select sha1 from %s" % table):
469
 
                yield sha
 
533
                yield sha.encode('ascii')
470
534
 
471
535
 
472
536
class TdbCacheUpdater(CacheUpdater):
480
544
        self._commit = None
481
545
        self._entries = []
482
546
 
483
 
    def add_object(self, obj, ie, path):
484
 
        sha = obj.sha().digest()
485
 
        if obj.type_name == "commit":
486
 
            self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
487
 
            assert type(ie) is dict, "was %r" % ie
488
 
            type_data = (self.revid, obj.tree, ie["testament3-sha1"])
 
547
    def add_object(self, obj, bzr_key_data, path):
 
548
        if isinstance(obj, tuple):
 
549
            (type_name, hexsha) = obj
 
550
            sha = hex_to_sha(hexsha)
 
551
        else:
 
552
            type_name = obj.type_name.decode('ascii')
 
553
            sha = obj.sha().digest()
 
554
        if type_name == "commit":
 
555
            self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
 
556
            if type(bzr_key_data) is not dict:
 
557
                raise TypeError(bzr_key_data)
 
558
            type_data = (self.revid, obj.tree)
 
559
            try:
 
560
                type_data += (bzr_key_data["testament3-sha1"],)
 
561
            except KeyError:
 
562
                pass
489
563
            self._commit = obj
490
 
        elif obj.type_name == "blob":
491
 
            if ie is None:
492
 
                return
493
 
            self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
494
 
            type_data = (ie.file_id, ie.revision)
495
 
        elif obj.type_name == "tree":
496
 
            if ie is None:
497
 
                return
498
 
            type_data = (ie.file_id, self.revid)
 
564
        elif type_name == "blob":
 
565
            if bzr_key_data is None:
 
566
                return
 
567
            self.db[b"\0".join(
 
568
                (b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
 
569
            type_data = bzr_key_data
 
570
        elif type_name == "tree":
 
571
            if bzr_key_data is None:
 
572
                return
 
573
            type_data = bzr_key_data
499
574
        else:
500
575
            raise AssertionError
501
 
        self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
 
576
        entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
 
577
        key = b"git\0" + sha
 
578
        try:
 
579
            oldval = self.db[key]
 
580
        except KeyError:
 
581
            self.db[key] = entry
 
582
        else:
 
583
            if not oldval.endswith(b'\n'):
 
584
                self.db[key] = b"".join([oldval, b"\n", entry])
 
585
            else:
 
586
                self.db[key] = b"".join([oldval, entry])
502
587
 
503
588
    def finish(self):
504
589
        if self._commit is None:
506
591
        return self._commit
507
592
 
508
593
 
509
 
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
 
594
def TdbBzrGitCache(p):
 
595
    return BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
 
596
 
510
597
 
511
598
class TdbGitCacheFormat(BzrGitCacheFormat):
512
599
    """Cache format for tdb-based caches."""
513
600
 
514
601
    def get_format_string(self):
515
 
        return 'bzr-git sha map version 3 using tdb\n'
 
602
        return b'bzr-git sha map version 3 using tdb\n'
516
603
 
517
604
    def open(self, transport):
518
605
        try:
519
606
            basepath = transport.local_abspath(".")
520
 
        except bzrlib.errors.NotLocalUrl:
 
607
        except bzr_errors.NotLocalUrl:
521
608
            basepath = get_cache_dir()
522
609
        try:
523
610
            return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
547
634
        if path is None:
548
635
            self.db = {}
549
636
        else:
550
 
            if not mapdbs().has_key(path):
 
637
            if path not in mapdbs():
551
638
                mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
552
 
                                          os.O_RDWR|os.O_CREAT)
 
639
                                         os.O_RDWR | os.O_CREAT)
553
640
            self.db = mapdbs()[path]
554
641
        try:
555
 
            if int(self.db["version"]) not in (2, 3):
556
 
                trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
557
 
                              self.db["version"], self.TDB_MAP_VERSION)
 
642
            if int(self.db[b"version"]) not in (2, 3):
 
643
                trace.warning(
 
644
                    "SHA Map is incompatible (%s -> %d), rebuilding database.",
 
645
                    self.db[b"version"], self.TDB_MAP_VERSION)
558
646
                self.db.clear()
559
647
        except KeyError:
560
648
            pass
561
 
        self.db["version"] = str(self.TDB_MAP_VERSION)
 
649
        self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
562
650
 
563
651
    def start_write_group(self):
564
652
        """Start writing changes."""
576
664
        return "%s(%r)" % (self.__class__.__name__, self.path)
577
665
 
578
666
    def lookup_commit(self, revid):
579
 
        return sha_to_hex(self.db["commit\0" + revid][:20])
 
667
        try:
 
668
            return sha_to_hex(self.db[b"commit\0" + revid][:20])
 
669
        except KeyError:
 
670
            raise KeyError("No cache entry for %r" % revid)
580
671
 
581
672
    def lookup_blob_id(self, fileid, revision):
582
 
        return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
 
673
        return sha_to_hex(self.db[b"\0".join((b"blob", fileid, revision))])
583
674
 
584
675
    def lookup_git_sha(self, sha):
585
676
        """Lookup a Git sha in the database.
592
683
        """
593
684
        if len(sha) == 40:
594
685
            sha = hex_to_sha(sha)
595
 
        data = self.db["git\0" + sha].split("\0")
596
 
        if data[0] == "commit":
597
 
            if len(data) == 3:
598
 
                return (data[0], (data[1], data[2], {}))
 
686
        value = self.db[b"git\0" + sha]
 
687
        for data in value.splitlines():
 
688
            data = data.split(b"\0")
 
689
            type_name = data[0].decode('ascii')
 
690
            if type_name == "commit":
 
691
                if len(data) == 3:
 
692
                    yield (type_name, (data[1], data[2], {}))
 
693
                else:
 
694
                    yield (type_name, (data[1], data[2],
 
695
                                       {"testament3-sha1": data[3]}))
 
696
            elif type_name in ("tree", "blob"):
 
697
                yield (type_name, tuple(data[1:]))
599
698
            else:
600
 
                return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
601
 
        else:
602
 
            return (data[0], tuple(data[1:]))
 
699
                raise AssertionError("unknown type %r" % type_name)
603
700
 
604
701
    def missing_revisions(self, revids):
605
702
        ret = set()
606
703
        for revid in revids:
607
 
            if self.db.get("commit\0" + revid) is None:
 
704
            if self.db.get(b"commit\0" + revid) is None:
608
705
                ret.add(revid)
609
706
        return ret
610
707
 
 
708
    def _keys(self):
 
709
        return self.db.keys()
 
710
 
611
711
    def revids(self):
612
712
        """List the revision ids known."""
613
 
        for key in self.db.iterkeys():
614
 
            if key.startswith("commit\0"):
 
713
        for key in self._keys():
 
714
            if key.startswith(b"commit\0"):
615
715
                yield key[7:]
616
716
 
617
717
    def sha1s(self):
618
718
        """List the SHA1s."""
619
 
        for key in self.db.iterkeys():
620
 
            if key.startswith("git\0"):
 
719
        for key in self._keys():
 
720
            if key.startswith(b"git\0"):
621
721
                yield sha_to_hex(key[4:])
622
722
 
623
723
 
628
728
 
629
729
    def add(self, obj):
630
730
        self._vf.insert_record_stream(
631
 
            [versionedfile.ChunkedContentFactory((obj.id,), [], None,
632
 
                obj.as_legacy_object_chunks())])
 
731
            [versionedfile.ChunkedContentFactory(
 
732
                (obj.id,), [], None, obj.as_legacy_object_chunks())])
633
733
 
634
734
    def __getitem__(self, sha):
635
735
        stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
636
 
        entry = stream.next() 
 
736
        entry = next(stream)
637
737
        if entry.storage_kind == 'absent':
638
738
            raise KeyError(sha)
639
739
        return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
640
740
 
641
741
 
642
 
class GitObjectStoreContentCache(ContentCache):
643
 
 
644
 
    def __init__(self, store):
645
 
        self.store = store
646
 
 
647
 
    def add_multi(self, objs):
648
 
        self.store.add_objects(objs)
649
 
 
650
 
    def add(self, obj, path):
651
 
        self.store.add_object(obj)
652
 
 
653
 
    def __getitem__(self, sha):
654
 
        return self.store[sha]
655
 
 
656
 
 
657
742
class IndexCacheUpdater(CacheUpdater):
658
743
 
659
744
    def __init__(self, cache, rev):
662
747
        self.parent_revids = rev.parent_ids
663
748
        self._commit = None
664
749
        self._entries = []
665
 
        self._cache_objs = set()
666
750
 
667
 
    def add_object(self, obj, ie, path):
668
 
        if obj.type_name == "commit":
 
751
    def add_object(self, obj, bzr_key_data, path):
 
752
        if isinstance(obj, tuple):
 
753
            (type_name, hexsha) = obj
 
754
        else:
 
755
            type_name = obj.type_name.decode('ascii')
 
756
            hexsha = obj.id
 
757
        if type_name == "commit":
669
758
            self._commit = obj
670
 
            assert type(ie) is dict
671
 
            self.cache.idmap._add_git_sha(obj.id, "commit",
672
 
                (self.revid, obj.tree, ie))
673
 
            self.cache.idmap._add_node(("commit", self.revid, "X"),
674
 
                " ".join((obj.id, obj.tree)))
675
 
            self._cache_objs.add((obj, path))
676
 
        elif obj.type_name == "blob":
677
 
            self.cache.idmap._add_git_sha(obj.id, "blob",
678
 
                (ie.file_id, ie.revision))
679
 
            self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
680
 
            if ie.kind == "symlink":
681
 
                self._cache_objs.add((obj, path))
682
 
        elif obj.type_name == "tree":
683
 
            self.cache.idmap._add_git_sha(obj.id, "tree",
684
 
                (ie.file_id, self.revid))
685
 
            self._cache_objs.add((obj, path))
 
759
            if type(bzr_key_data) is not dict:
 
760
                raise TypeError(bzr_key_data)
 
761
            self.cache.idmap._add_git_sha(hexsha, b"commit",
 
762
                                          (self.revid, obj.tree, bzr_key_data))
 
763
            self.cache.idmap._add_node((b"commit", self.revid, b"X"),
 
764
                                       b" ".join((hexsha, obj.tree)))
 
765
        elif type_name == "blob":
 
766
            self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
 
767
            self.cache.idmap._add_node((b"blob", bzr_key_data[0],
 
768
                                        bzr_key_data[1]), hexsha)
 
769
        elif type_name == "tree":
 
770
            self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
686
771
        else:
687
772
            raise AssertionError
688
773
 
689
774
    def finish(self):
690
 
        self.cache.content_cache.add_multi(self._cache_objs)
691
775
        return self._commit
692
776
 
693
777
 
694
778
class IndexBzrGitCache(BzrGitCache):
695
779
 
696
780
    def __init__(self, transport=None):
697
 
        mapper = versionedfile.ConstantMapper("trees")
698
781
        shamap = IndexGitShaMap(transport.clone('index'))
699
 
        #trees_store = knit.make_file_factory(True, mapper)(transport)
700
 
        #content_cache = VersionedFilesContentCache(trees_store)
701
 
        from bzrlib.plugins.git.transportgit import TransportObjectStore
702
 
        store = TransportObjectStore(transport.clone('objects'))
703
 
        content_cache = GitObjectStoreContentCache(store)
704
 
        super(IndexBzrGitCache, self).__init__(shamap, content_cache,
705
 
                IndexCacheUpdater)
 
782
        super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
706
783
 
707
784
 
708
785
class IndexGitCacheFormat(BzrGitCacheFormat):
709
786
 
710
787
    def get_format_string(self):
711
 
        return 'bzr-git sha map with git object cache version 1\n'
 
788
        return b'bzr-git sha map with git object cache version 1\n'
712
789
 
713
790
    def initialize(self, transport):
714
791
        super(IndexGitCacheFormat, self).initialize(transport)
715
792
        transport.mkdir('index')
716
793
        transport.mkdir('objects')
717
 
        from bzrlib.plugins.git.transportgit import TransportObjectStore
 
794
        from .transportgit import TransportObjectStore
718
795
        TransportObjectStore.init(transport.clone('objects'))
719
796
 
720
797
    def open(self, transport):
726
803
 
727
804
    BTree Index file with the following contents:
728
805
 
729
 
    ("git", <sha1>) -> "<type> <type-data1> <type-data2>"
730
 
    ("commit", <revid>) -> "<sha1> <tree-id>"
 
806
    ("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
 
807
    ("commit", <revid>, "X") -> "<sha1> <tree-id>"
731
808
    ("blob", <fileid>, <revid>) -> <sha1>
732
809
 
733
810
    """
734
811
 
735
812
    def __init__(self, transport=None):
 
813
        self._name = None
736
814
        if transport is None:
737
815
            self._transport = None
738
816
            self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
744
822
            for name in self._transport.list_dir("."):
745
823
                if not name.endswith(".rix"):
746
824
                    continue
747
 
                x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
748
 
                    self._transport.stat(name).st_size)
 
825
                x = _mod_btree_index.BTreeGraphIndex(
 
826
                    self._transport, name, self._transport.stat(name).st_size)
749
827
                self._index.insert_index(0, x)
750
828
 
751
829
    @classmethod
754
832
        if transport is not None:
755
833
            try:
756
834
                transport.mkdir('git')
757
 
            except bzrlib.errors.FileExists:
 
835
            except bzr_errors.FileExists:
758
836
                pass
759
837
            return cls(transport.clone('git'))
760
 
        from bzrlib.transport import get_transport
761
 
        return cls(get_transport(get_cache_dir()))
 
838
        return cls(get_transport_from_path(get_cache_dir()))
762
839
 
763
840
    def __repr__(self):
764
841
        if self._transport is not None:
767
844
            return "%s()" % (self.__class__.__name__)
768
845
 
769
846
    def repack(self):
770
 
        assert self._builder is None
 
847
        if self._builder is not None:
 
848
            raise bzr_errors.BzrError('builder already open')
771
849
        self.start_write_group()
772
 
        for _, key, value in self._index.iter_all_entries():
773
 
            self._builder.add_node(key, value)
 
850
        self._builder.add_nodes(
 
851
            ((key, value) for (_, key, value) in
 
852
                self._index.iter_all_entries()))
774
853
        to_remove = []
775
854
        for name in self._transport.list_dir('.'):
776
855
            if name.endswith('.rix'):
781
860
            self._transport.rename(name, name + '.old')
782
861
 
783
862
    def start_write_group(self):
784
 
        assert self._builder is None
 
863
        if self._builder is not None:
 
864
            raise bzr_errors.BzrError('builder already open')
785
865
        self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
786
866
        self._name = osutils.sha()
787
867
 
788
868
    def commit_write_group(self):
789
 
        assert self._builder is not None
 
869
        if self._builder is None:
 
870
            raise bzr_errors.BzrError('builder not open')
790
871
        stream = self._builder.finish()
791
872
        name = self._name.hexdigest() + ".rix"
792
873
        size = self._transport.put_file(name, stream)
796
877
        self._name = None
797
878
 
798
879
    def abort_write_group(self):
799
 
        assert self._builder is not None
 
880
        if self._builder is None:
 
881
            raise bzr_errors.BzrError('builder not open')
800
882
        self._builder = None
801
883
        self._name = None
802
884
 
803
885
    def _add_node(self, key, value):
804
886
        try:
 
887
            self._get_entry(key)
 
888
        except KeyError:
805
889
            self._builder.add_node(key, value)
806
 
        except bzrlib.errors.BadIndexDuplicateKey:
807
 
            # Multiple bzr objects can have the same contents
 
890
            return False
 
891
        else:
808
892
            return True
809
 
        else:
810
 
            return False
811
893
 
812
894
    def _get_entry(self, key):
813
895
        entries = self._index.iter_entries([key])
814
896
        try:
815
 
            return entries.next()[2]
 
897
            return next(entries)[2]
816
898
        except StopIteration:
817
899
            if self._builder is None:
818
900
                raise KeyError
819
901
            entries = self._builder.iter_entries([key])
820
902
            try:
821
 
                return entries.next()[2]
 
903
                return next(entries)[2]
822
904
            except StopIteration:
823
905
                raise KeyError
824
906
 
825
 
    def _iter_keys_prefix(self, prefix):
 
907
    def _iter_entries_prefix(self, prefix):
826
908
        for entry in self._index.iter_entries_prefix([prefix]):
827
 
            yield entry[1]
 
909
            yield (entry[1], entry[2])
828
910
        if self._builder is not None:
829
911
            for entry in self._builder.iter_entries_prefix([prefix]):
830
 
                yield entry[1]
 
912
                yield (entry[1], entry[2])
831
913
 
832
914
    def lookup_commit(self, revid):
833
 
        return self._get_entry(("commit", revid, "X"))[:40]
 
915
        return self._get_entry((b"commit", revid, b"X"))[:40]
834
916
 
835
917
    def _add_git_sha(self, hexsha, type, type_data):
836
918
        if hexsha is not None:
837
919
            self._name.update(hexsha)
838
 
            if type == "commit":
839
 
                td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
 
920
            if type == b"commit":
 
921
                td = (type_data[0], type_data[1])
 
922
                try:
 
923
                    td += (type_data[2]["testament3-sha1"],)
 
924
                except KeyError:
 
925
                    pass
840
926
            else:
841
927
                td = type_data
842
 
            self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
 
928
            self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
843
929
        else:
844
930
            # This object is not represented in Git - perhaps an empty
845
931
            # directory?
846
 
            self._name.update(type + " ".join(type_data))
 
932
            self._name.update(type + b" ".join(type_data))
847
933
 
848
934
    def lookup_blob_id(self, fileid, revision):
849
 
        return self._get_entry(("blob", fileid, revision))
 
935
        return self._get_entry((b"blob", fileid, revision))
850
936
 
851
937
    def lookup_git_sha(self, sha):
852
938
        if len(sha) == 20:
853
939
            sha = sha_to_hex(sha)
854
 
        data = self._get_entry(("git", sha, "X")).split(" ", 3)
855
 
        if data[0] == "commit":
856
 
            return ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
 
940
        value = self._get_entry((b"git", sha, b"X"))
 
941
        data = value.split(b" ", 3)
 
942
        if data[0] == b"commit":
 
943
            try:
 
944
                if data[3]:
 
945
                    verifiers = {"testament3-sha1": data[3]}
 
946
                else:
 
947
                    verifiers = {}
 
948
            except IndexError:
 
949
                verifiers = {}
 
950
            yield ("commit", (data[1], data[2], verifiers))
857
951
        else:
858
 
            return (data[0], tuple(data[1:]))
 
952
            yield (data[0].decode('ascii'), tuple(data[1:]))
859
953
 
860
954
    def revids(self):
861
955
        """List the revision ids known."""
862
 
        for key in self._iter_keys_prefix(("commit", None, None)):
 
956
        for key, value in self._iter_entries_prefix((b"commit", None, None)):
863
957
            yield key[1]
864
958
 
865
959
    def missing_revisions(self, revids):
866
960
        """Return set of all the revisions that are not present."""
867
961
        missing_revids = set(revids)
868
962
        for _, key, value in self._index.iter_entries((
869
 
            ("commit", revid, "X") for revid in revids)):
 
963
                (b"commit", revid, b"X") for revid in revids)):
870
964
            missing_revids.remove(key[1])
871
965
        return missing_revids
872
966
 
873
967
    def sha1s(self):
874
968
        """List the SHA1s."""
875
 
        for key in self._iter_keys_prefix(("git", None, None)):
 
969
        for key, value in self._iter_entries_prefix((b"git", None, None)):
876
970
            yield key[1]
877
971
 
878
972
 
879
973
formats = registry.Registry()
880
974
formats.register(TdbGitCacheFormat().get_format_string(),
881
 
    TdbGitCacheFormat())
 
975
                 TdbGitCacheFormat())
882
976
formats.register(SqliteGitCacheFormat().get_format_string(),
883
 
    SqliteGitCacheFormat())
 
977
                 SqliteGitCacheFormat())
884
978
formats.register(IndexGitCacheFormat().get_format_string(),
885
 
    IndexGitCacheFormat())
 
979
                 IndexGitCacheFormat())
886
980
# In the future, this will become the default:
887
 
# formats.register('default', IndexGitCacheFormat())
888
 
try:
889
 
    import tdb
890
 
except ImportError:
891
 
    formats.register('default', SqliteGitCacheFormat())
892
 
else:
893
 
    formats.register('default', TdbGitCacheFormat())
894
 
 
 
981
formats.register('default', IndexGitCacheFormat())
895
982
 
896
983
 
897
984
def migrate_ancient_formats(repo_transport):
898
 
    # Prefer migrating git.db over git.tdb, since the latter may not 
 
985
    # Migrate older cache formats
 
986
    repo_transport = remove_readonly_transport_decorator(repo_transport)
 
987
    has_sqlite = repo_transport.has("git.db")
 
988
    has_tdb = repo_transport.has("git.tdb")
 
989
    if not has_sqlite or has_tdb:
 
990
        return
 
991
    try:
 
992
        repo_transport.mkdir("git")
 
993
    except bzr_errors.FileExists:
 
994
        return
 
995
    # Prefer migrating git.db over git.tdb, since the latter may not
899
996
    # be openable on some platforms.
900
 
    if repo_transport.has("git.db"):
 
997
    if has_sqlite:
901
998
        SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
902
999
        repo_transport.rename("git.db", "git/idmap.db")
903
 
    elif repo_transport.has("git.tdb"):
 
1000
    elif has_tdb:
904
1001
        TdbGitCacheFormat().initialize(repo_transport.clone("git"))
905
1002
        repo_transport.rename("git.tdb", "git/idmap.tdb")
906
1003
 
907
1004
 
908
1005
def remove_readonly_transport_decorator(transport):
909
1006
    if transport.is_readonly():
910
 
        return transport._decorated
 
1007
        try:
 
1008
            return transport._decorated
 
1009
        except AttributeError:
 
1010
            raise bzr_errors.ReadOnlyError(transport)
911
1011
    return transport
912
1012
 
913
1013
 
922
1022
    """
923
1023
    repo_transport = getattr(repository, "_transport", None)
924
1024
    if repo_transport is not None:
925
 
        # Migrate older cache formats
926
 
        repo_transport = remove_readonly_transport_decorator(repo_transport)
927
1025
        try:
928
 
            repo_transport.mkdir("git")
929
 
        except bzrlib.errors.FileExists:
930
 
            pass
931
 
        else:
932
1026
            migrate_ancient_formats(repo_transport)
 
1027
        except bzr_errors.ReadOnlyError:
 
1028
            pass  # Not much we can do
933
1029
    return BzrGitCacheFormat.from_repository(repository)