42
43
from ..transport import (
43
get_transport_from_path,
47
48
def get_cache_dir():
48
path = os.path.join(bedding.cache_dir(), "git")
49
if not os.path.isdir(path):
50
from xdg.BaseDirectory import xdg_cache_home
52
from ..config import config_dir
53
ret = os.path.join(config_dir(), "git")
55
ret = os.path.join(xdg_cache_home, "breezy", "git")
56
if not os.path.isdir(ret):
54
61
def get_remote_cache_transport(repository):
55
"""Retrieve the transport to use when accessing (unwritable) remote
62
"""Retrieve the transport to use when accessing (unwritable) remote
58
65
uuid = getattr(repository, "uuid", None)
62
69
path = os.path.join(get_cache_dir(), uuid)
63
70
if not os.path.isdir(path):
65
return get_transport_from_path(path)
72
return get_transport(path)
68
75
def check_pysqlite_version(sqlite3):
69
76
"""Check that sqlite library is compatible.
72
if (sqlite3.sqlite_version_info[0] < 3
73
or (sqlite3.sqlite_version_info[0] == 3 and
74
sqlite3.sqlite_version_info[1] < 3)):
79
if (sqlite3.sqlite_version_info[0] < 3 or
80
(sqlite3.sqlite_version_info[0] == 3 and
81
sqlite3.sqlite_version_info[1] < 3)):
75
82
trace.warning('Needs at least sqlite 3.3.x')
76
83
raise bzr_errors.BzrError("incompatible sqlite library")
82
88
check_pysqlite_version(sqlite3)
83
except (ImportError, bzr_errors.BzrError):
89
except (ImportError, bzr_errors.BzrError) as e:
84
90
from pysqlite2 import dbapi2 as sqlite3
85
91
check_pysqlite_version(sqlite3)
87
93
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
89
95
raise bzr_errors.BzrError("missing sqlite library")
92
98
_mapdbs = threading.local()
96
100
"""Get a cache for this thread's db connections."""
209
213
"""Open a cache file for a repository.
211
215
This will use the repository's transport to store the cache file, or
212
use the users global cache directory if the repository has no
216
use the users global cache directory if the repository has no
213
217
transport associated with it.
215
219
:param repository: Repository to open the cache for
218
222
from ..transport.local import LocalTransport
219
223
repo_transport = getattr(repository, "_transport", None)
220
if (repo_transport is not None
221
and isinstance(repo_transport, LocalTransport)):
224
if (repo_transport is not None and
225
isinstance(repo_transport, LocalTransport)):
222
226
# Even if we don't write to this repo, we should be able
223
227
# to update its cache.
225
repo_transport = remove_readonly_transport_decorator(
229
repo_transport = remove_readonly_transport_decorator(repo_transport)
227
230
except bzr_errors.ReadOnlyError:
264
267
self._cache_updater_klass = cache_updater_klass
266
269
def get_updater(self, rev):
267
"""Update an object that implements the CacheUpdater interface for
270
"""Update an object that implements the CacheUpdater interface for
268
271
updating this cache.
270
273
return self._cache_updater_klass(self, rev)
273
def DictBzrGitCache():
274
return BzrGitCache(DictGitShaMap(), DictCacheUpdater)
276
DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), DictCacheUpdater)
277
279
class DictCacheUpdater(CacheUpdater):
288
290
if isinstance(obj, tuple):
289
291
(type_name, hexsha) = obj
291
type_name = obj.type_name.decode('ascii')
293
type_name = obj.type_name
293
if not isinstance(hexsha, bytes):
294
raise TypeError(hexsha)
295
295
if type_name == "commit":
296
296
self._commit = obj
297
297
if type(bzr_key_data) is not dict:
302
302
elif type_name in ("blob", "tree"):
303
303
if bzr_key_data is not None:
304
304
key = type_data = bzr_key_data
305
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
306
type_data[0]] = hexsha
305
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = hexsha
308
307
raise AssertionError
309
308
entry = (type_name, type_data)
327
326
return self._by_fileid[revision][fileid]
329
328
def lookup_git_sha(self, sha):
330
if not isinstance(sha, bytes):
332
for entry in self._by_sha[sha].values():
329
for entry in self._by_sha[sha].itervalues():
335
332
def lookup_tree_id(self, fileid, revision):
339
336
return self._by_revid[revid]
341
338
def revids(self):
342
for key, entries in self._by_sha.items():
339
for key, entries in self._by_sha.iteritems():
343
340
for (type, type_data) in entries.values():
344
341
if type == "commit":
345
342
yield type_data[0]
348
return self._by_sha.keys()
345
return self._by_sha.iterkeys()
351
348
class SqliteCacheUpdater(CacheUpdater):
362
359
if isinstance(obj, tuple):
363
360
(type_name, hexsha) = obj
365
type_name = obj.type_name.decode('ascii')
362
type_name = obj.type_name
367
if not isinstance(hexsha, bytes):
368
raise TypeError(hexsha)
369
364
if type_name == "commit":
370
365
self._commit = obj
371
366
if type(bzr_key_data) is not dict:
390
385
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
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))
388
"replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
389
(self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
397
390
return self._commit
400
def SqliteBzrGitCache(p):
401
return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
393
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
404
396
class SqliteGitCacheFormat(BzrGitCacheFormat):
440
432
revid text not null
442
434
create index if not exists blobs_sha1 on blobs(sha1);
443
create unique index if not exists blobs_fileid_revid on blobs(
435
create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
445
436
create table if not exists trees(
446
437
sha1 text unique not null check(length(sha1) == 40),
447
438
fileid text not null,
448
439
revid text not null
450
441
create unique index if not exists trees_sha1 on trees(sha1);
451
create unique index if not exists trees_fileid_revid on trees(
442
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
455
445
self.db.executescript(
456
446
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
457
447
except sqlite3.OperationalError:
458
pass # Column already exists.
448
pass # Column already exists.
460
450
def __repr__(self):
461
451
return "%s(%r)" % (self.__class__.__name__, self.path)
463
453
def lookup_commit(self, revid):
464
cursor = self.db.execute("select sha1 from commits where revid = ?",
454
cursor = self.db.execute("select sha1 from commits where revid = ?",
466
456
row = cursor.fetchone()
467
457
if row is not None:
474
464
def lookup_blob_id(self, fileid, revision):
475
row = self.db.execute(
476
"select sha1 from blobs where fileid = ? and revid = ?",
477
(fileid, revision)).fetchone()
465
row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
478
466
if row is not None:
480
468
raise KeyError(fileid)
482
470
def lookup_tree_id(self, fileid, revision):
483
row = self.db.execute(
484
"select sha1 from trees where fileid = ? and revid = ?",
485
(fileid, revision)).fetchone()
471
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
486
472
if row is not None:
488
474
raise KeyError(fileid)
497
483
blob: fileid, revid
500
cursor = self.db.execute(
501
"select revid, tree_sha, testament3_sha1 from commits where "
486
cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
503
487
for row in cursor.fetchall():
505
489
if row[2] is not None:
509
493
yield ("commit", (row[0], row[1], verifiers))
510
cursor = self.db.execute(
511
"select fileid, revid from blobs where sha1 = ?", (sha,))
494
cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
512
495
for row in cursor.fetchall():
514
497
yield ("blob", row)
515
cursor = self.db.execute(
516
"select fileid, revid from trees where sha1 = ?", (sha,))
498
cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
517
499
for row in cursor.fetchall():
519
501
yield ("tree", row)
528
510
"""List the SHA1s."""
529
511
for table in ("blobs", "commits", "trees"):
530
512
for (sha,) in self.db.execute("select sha1 from %s" % table):
531
yield sha.encode('ascii')
534
516
class TdbCacheUpdater(CacheUpdater):
547
529
(type_name, hexsha) = obj
548
530
sha = hex_to_sha(hexsha)
550
type_name = obj.type_name.decode('ascii')
532
type_name = obj.type_name
551
533
sha = obj.sha().digest()
552
534
if type_name == "commit":
553
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
535
self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
554
536
if type(bzr_key_data) is not dict:
555
537
raise TypeError(bzr_key_data)
556
538
type_data = (self.revid, obj.tree)
562
544
elif type_name == "blob":
563
545
if bzr_key_data is None:
566
(b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
547
self.db["\0".join(("blob", bzr_key_data[0], bzr_key_data[1]))] = sha
567
548
type_data = bzr_key_data
568
549
elif type_name == "tree":
569
550
if bzr_key_data is None:
571
552
type_data = bzr_key_data
573
554
raise AssertionError
574
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
555
entry = "\0".join((type_name, ) + type_data) + "\n"
577
558
oldval = self.db[key]
579
560
self.db[key] = entry
581
if not oldval.endswith(b'\n'):
582
self.db[key] = b"".join([oldval, b"\n", entry])
562
if oldval[-1] != "\n":
563
self.db[key] = "".join([oldval, "\n", entry])
584
self.db[key] = b"".join([oldval, entry])
565
self.db[key] = "".join([oldval, entry])
586
567
def finish(self):
587
568
if self._commit is None:
602
582
def open(self, transport):
604
basepath = transport.local_abspath(".")
584
basepath = transport.local_abspath(".").encode(osutils._fs_enc)
605
585
except bzr_errors.NotLocalUrl:
606
586
basepath = get_cache_dir()
587
if type(basepath) is not str:
588
raise TypeError(basepath)
608
590
return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
609
591
except ImportError:
635
if path not in mapdbs():
617
if type(path) is not str:
618
raise TypeError(path)
619
if not mapdbs().has_key(path):
636
620
mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
637
os.O_RDWR | os.O_CREAT)
621
os.O_RDWR|os.O_CREAT)
638
622
self.db = mapdbs()[path]
640
if int(self.db[b"version"]) not in (2, 3):
642
"SHA Map is incompatible (%s -> %d), rebuilding database.",
643
self.db[b"version"], self.TDB_MAP_VERSION)
624
if int(self.db["version"]) not in (2, 3):
625
trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
626
self.db["version"], self.TDB_MAP_VERSION)
647
self.db[b"version"] = b'%d' % self.TDB_MAP_VERSION
630
self.db["version"] = str(self.TDB_MAP_VERSION)
649
632
def start_write_group(self):
650
633
"""Start writing changes."""
664
647
def lookup_commit(self, revid):
666
return sha_to_hex(self.db[b"commit\0" + revid][:20])
649
return sha_to_hex(self.db["commit\0" + revid][:20])
668
651
raise KeyError("No cache entry for %r" % revid)
670
653
def lookup_blob_id(self, fileid, revision):
671
return sha_to_hex(self.db[b"\0".join((b"blob", fileid, revision))])
654
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
673
656
def lookup_git_sha(self, sha):
674
657
"""Lookup a Git sha in the database.
682
665
if len(sha) == 40:
683
666
sha = hex_to_sha(sha)
684
value = self.db[b"git\0" + sha]
667
value = self.db["git\0" + sha]
685
668
for data in value.splitlines():
686
data = data.split(b"\0")
687
type_name = data[0].decode('ascii')
688
if type_name == "commit":
669
data = data.split("\0")
670
if data[0] == "commit":
689
671
if len(data) == 3:
690
yield (type_name, (data[1], data[2], {}))
672
yield (data[0], (data[1], data[2], {}))
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:]))
674
yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
675
elif data[0] in ("tree", "blob"):
676
yield (data[0], tuple(data[1:]))
697
raise AssertionError("unknown type %r" % type_name)
678
raise AssertionError("unknown type %r" % data[0])
699
680
def missing_revisions(self, revids):
701
682
for revid in revids:
702
if self.db.get(b"commit\0" + revid) is None:
683
if self.db.get("commit\0" + revid) is None:
707
return self.db.keys()
709
687
def revids(self):
710
688
"""List the revision ids known."""
711
for key in self._keys():
712
if key.startswith(b"commit\0"):
689
for key in self.db.iterkeys():
690
if key.startswith("commit\0"):
716
694
"""List the SHA1s."""
717
for key in self._keys():
718
if key.startswith(b"git\0"):
695
for key in self.db.iterkeys():
696
if key.startswith("git\0"):
719
697
yield sha_to_hex(key[4:])
727
705
def add(self, obj):
728
706
self._vf.insert_record_stream(
729
[versionedfile.ChunkedContentFactory(
730
(obj.id,), [], None, obj.as_legacy_object_chunks())])
707
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
708
obj.as_legacy_object_chunks())])
732
710
def __getitem__(self, sha):
733
711
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
712
entry = stream.next()
735
713
if entry.storage_kind == 'absent':
736
714
raise KeyError(sha)
737
715
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
750
728
if isinstance(obj, tuple):
751
729
(type_name, hexsha) = obj
753
type_name = obj.type_name.decode('ascii')
731
type_name = obj.type_name
755
733
if type_name == "commit":
756
734
self._commit = obj
757
735
if type(bzr_key_data) is not dict:
758
736
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)))
737
self.cache.idmap._add_git_sha(hexsha, "commit",
738
(self.revid, obj.tree, bzr_key_data))
739
self.cache.idmap._add_node(("commit", self.revid, "X"),
740
" ".join((hexsha, obj.tree)))
763
741
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)
742
self.cache.idmap._add_git_sha(hexsha, "blob", bzr_key_data)
743
self.cache.idmap._add_node(("blob", bzr_key_data[0],
744
bzr_key_data[1]), hexsha)
767
745
elif type_name == "tree":
768
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
746
self.cache.idmap._add_git_sha(hexsha, "tree", bzr_key_data)
770
748
raise AssertionError
776
754
class IndexBzrGitCache(BzrGitCache):
778
756
def __init__(self, transport=None):
757
mapper = versionedfile.ConstantMapper("trees")
779
758
shamap = IndexGitShaMap(transport.clone('index'))
759
from .transportgit import TransportObjectStore
780
760
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
820
800
for name in self._transport.list_dir("."):
821
801
if not name.endswith(".rix"):
823
x = _mod_btree_index.BTreeGraphIndex(
824
self._transport, name, self._transport.stat(name).st_size)
803
x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
804
self._transport.stat(name).st_size)
825
805
self._index.insert_index(0, x)
833
813
except bzr_errors.FileExists:
835
815
return cls(transport.clone('git'))
836
return cls(get_transport_from_path(get_cache_dir()))
816
from ..transport import get_transport
817
return cls(get_transport(get_cache_dir()))
838
819
def __repr__(self):
839
820
if self._transport is not None:
844
825
def repack(self):
845
826
if self._builder is not None:
846
raise bzr_errors.BzrError('builder already open')
827
raise errors.BzrError('builder already open')
847
828
self.start_write_group()
848
829
self._builder.add_nodes(
849
830
((key, value) for (_, key, value) in
860
841
def start_write_group(self):
861
842
if self._builder is not None:
862
raise bzr_errors.BzrError('builder already open')
843
raise errors.BzrError('builder already open')
863
844
self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
864
845
self._name = osutils.sha()
866
847
def commit_write_group(self):
867
848
if self._builder is None:
868
raise bzr_errors.BzrError('builder not open')
849
raise errors.BzrError('builder not open')
869
850
stream = self._builder.finish()
870
851
name = self._name.hexdigest() + ".rix"
871
852
size = self._transport.put_file(name, stream)
910
891
yield (entry[1], entry[2])
912
893
def lookup_commit(self, revid):
913
return self._get_entry((b"commit", revid, b"X"))[:40]
894
return self._get_entry(("commit", revid, "X"))[:40]
915
896
def _add_git_sha(self, hexsha, type, type_data):
916
897
if hexsha is not None:
917
898
self._name.update(hexsha)
918
if type == b"commit":
919
900
td = (type_data[0], type_data[1])
921
902
td += (type_data[2]["testament3-sha1"],)
926
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
907
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
928
909
# This object is not represented in Git - perhaps an empty
930
self._name.update(type + b" ".join(type_data))
911
self._name.update(type + " ".join(type_data))
932
913
def lookup_blob_id(self, fileid, revision):
933
return self._get_entry((b"blob", fileid, revision))
914
return self._get_entry(("blob", fileid, revision))
935
916
def lookup_git_sha(self, sha):
936
917
if len(sha) == 20:
937
918
sha = sha_to_hex(sha)
938
value = self._get_entry((b"git", sha, b"X"))
939
data = value.split(b" ", 3)
940
if data[0] == b"commit":
919
value = self._get_entry(("git", sha, "X"))
920
data = value.split(" ", 3)
921
if data[0] == "commit":
943
924
verifiers = {"testament3-sha1": data[3]}
948
929
yield ("commit", (data[1], data[2], verifiers))
950
yield (data[0].decode('ascii'), tuple(data[1:]))
931
yield (data[0], tuple(data[1:]))
952
933
def revids(self):
953
934
"""List the revision ids known."""
954
for key, value in self._iter_entries_prefix((b"commit", None, None)):
935
for key, value in self._iter_entries_prefix(("commit", None, None)):
957
938
def missing_revisions(self, revids):
958
939
"""Return set of all the revisions that are not present."""
959
940
missing_revids = set(revids)
960
941
for _, key, value in self._index.iter_entries((
961
(b"commit", revid, b"X") for revid in revids)):
942
("commit", revid, "X") for revid in revids)):
962
943
missing_revids.remove(key[1])
963
944
return missing_revids
966
947
"""List the SHA1s."""
967
for key, value in self._iter_entries_prefix((b"git", None, None)):
948
for key, value in self._iter_entries_prefix(("git", None, None)):
971
952
formats = registry.Registry()
972
953
formats.register(TdbGitCacheFormat().get_format_string(),
974
955
formats.register(SqliteGitCacheFormat().get_format_string(),
975
SqliteGitCacheFormat())
956
SqliteGitCacheFormat())
976
957
formats.register(IndexGitCacheFormat().get_format_string(),
977
IndexGitCacheFormat())
958
IndexGitCacheFormat())
978
959
# In the future, this will become the default:
979
960
formats.register('default', IndexGitCacheFormat())
982
964
def migrate_ancient_formats(repo_transport):
983
965
# Migrate older cache formats
984
966
repo_transport = remove_readonly_transport_decorator(repo_transport)
990
972
repo_transport.mkdir("git")
991
973
except bzr_errors.FileExists:
993
# Prefer migrating git.db over git.tdb, since the latter may not
975
# Prefer migrating git.db over git.tdb, since the latter may not
994
976
# be openable on some platforms.
996
978
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
1024
1006
migrate_ancient_formats(repo_transport)
1025
1007
except bzr_errors.ReadOnlyError:
1026
pass # Not much we can do
1008
pass # Not much we can do
1027
1009
return BzrGitCacheFormat.from_repository(repository)