/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: 2019-06-03 23:48:08 UTC
  • mfrom: (7316 work)
  • mto: This revision was merged to the branch mainline in revision 7328.
  • Revision ID: jelmer@jelmer.uk-20190603234808-15yk5c7054tj8e2b
Merge trunk.

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