/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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