32
btree_index as _mod_btree_index,
38
btree_index as _mod_btree_index,
39
from bzrlib.transport import (
42
from ..transport import (
43
get_transport_from_path,
44
47
def get_cache_dir():
46
from xdg.BaseDirectory import xdg_cache_home
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):
54
def get_remote_cache_transport(repository):
55
"""Retrieve the transport to use when accessing (unwritable) remote
58
uuid = getattr(repository, "uuid", None)
60
path = get_cache_dir()
51
ret = os.path.join(xdg_cache_home, "bazaar", "git")
52
if not os.path.isdir(ret):
57
def get_remote_cache_transport():
58
"""Retrieve the transport to use when accessing (unwritable) remote
61
return get_transport(get_cache_dir())
62
path = os.path.join(get_cache_dir(), uuid)
63
if not os.path.isdir(path):
65
return get_transport_from_path(path)
64
68
def check_pysqlite_version(sqlite3):
65
69
"""Check that sqlite library is compatible.
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
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)
82
87
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
84
raise bzrlib.errors.BzrError("missing sqlite library")
89
raise bzr_errors.BzrError("missing sqlite library")
87
92
_mapdbs = threading.local()
89
96
"""Get a cache for this thread's db connections."""
197
209
"""Open a cache file for a repository.
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.
203
215
:param repository: Repository to open the cache for
204
216
:return: A `BzrGitCache`
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)
212
repo_transport.mkdir('git')
213
except bzrlib.errors.FileExists:
215
transport = repo_transport.clone('git')
225
repo_transport = remove_readonly_transport_decorator(
227
except bzr_errors.ReadOnlyError:
231
repo_transport.mkdir('git')
232
except bzr_errors.FileExists:
234
transport = repo_transport.clone('git')
217
transport = get_remote_cache_transport()
237
if transport is None:
238
transport = get_remote_cache_transport(repository)
218
239
return cls.from_transport(transport)
221
242
class CacheUpdater(object):
222
243
"""Base class for objects that can update a bzr-git cache."""
224
def add_object(self, obj, ie, path):
245
def add_object(self, obj, bzr_key_data, path):
225
246
"""Add an object.
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
230
251
:param path: Path of the object (optional)
263
284
self._commit = None
264
285
self._entries = []
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
291
type_name = obj.type_name.decode('ascii')
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"):
274
if obj.type_name == "blob":
275
revision = ie.revision
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)
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
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
284
312
def finish(self):
285
313
if self._commit is None:
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
365
type_name = obj.type_name.decode('ascii')
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":
336
self._trees.append((obj.id, ie.file_id, self.revid))
337
elif obj.type_name == "blob":
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]))
341
381
raise AssertionError
350
390
"replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
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
358
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
400
def SqliteBzrGitCache(p):
401
return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
361
404
class SqliteGitCacheFormat(BzrGitCacheFormat):
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'
366
409
def open(self, transport):
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"))
447
496
tree: fileid, revid
448
497
blob: fileid, revid
450
row = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,)).fetchone()
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()
456
row = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,)).fetchone()
500
cursor = self.db.execute(
501
"select revid, tree_sha, testament3_sha1 from commits where "
503
for row in cursor.fetchall():
505
if row[2] is not None:
506
verifiers = {"testament3-sha1": row[2]}
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():
515
cursor = self.db.execute(
516
"select fileid, revid from trees where sha1 = ?", (sha,))
517
for row in cursor.fetchall():
461
523
def revids(self):
462
524
"""List the revision ids known."""
480
542
self._commit = None
481
543
self._entries = []
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)
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)
558
type_data += (bzr_key_data["testament3-sha1"],)
489
561
self._commit = obj
490
elif obj.type_name == "blob":
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":
498
type_data = (ie.file_id, self.revid)
562
elif type_name == "blob":
563
if bzr_key_data is None:
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:
571
type_data = bzr_key_data
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"
577
oldval = self.db[key]
581
if not oldval.endswith(b'\n'):
582
self.db[key] = b"".join([oldval, b"\n", entry])
584
self.db[key] = b"".join([oldval, entry])
503
586
def finish(self):
504
587
if self._commit is None:
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":
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":
690
yield (type_name, (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:]))
600
return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
602
return (data[0], tuple(data[1:]))
697
raise AssertionError("unknown type %r" % type_name)
604
699
def missing_revisions(self, revids):
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:
707
return self.db.keys()
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"):
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:])
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())])
634
732
def __getitem__(self, sha):
635
733
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
636
entry = stream.next()
637
735
if entry.storage_kind == 'absent':
638
736
raise KeyError(sha)
639
737
return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
642
class GitObjectStoreContentCache(ContentCache):
644
def __init__(self, store):
647
def add_multi(self, objs):
648
self.store.add_objects(objs)
650
def add(self, obj, path):
651
self.store.add_object(obj)
653
def __getitem__(self, sha):
654
return self.store[sha]
657
740
class IndexCacheUpdater(CacheUpdater):
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()
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
753
type_name = obj.type_name.decode('ascii')
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)
687
770
raise AssertionError
689
772
def finish(self):
690
self.cache.content_cache.add_multi(self._cache_objs)
691
773
return self._commit
694
776
class IndexBzrGitCache(BzrGitCache):
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,
780
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
708
783
class IndexGitCacheFormat(BzrGitCacheFormat):
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'
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'))
720
795
def open(self, transport):
796
875
self._name = None
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
803
883
def _add_node(self, key, value):
805
887
self._builder.add_node(key, value)
806
except bzrlib.errors.BadIndexDuplicateKey:
807
# Multiple bzr objects can have the same contents
812
892
def _get_entry(self, key):
813
893
entries = self._index.iter_entries([key])
815
return entries.next()[2]
895
return next(entries)[2]
816
896
except StopIteration:
817
897
if self._builder is None:
819
899
entries = self._builder.iter_entries([key])
821
return entries.next()[2]
901
return next(entries)[2]
822
902
except StopIteration:
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]):
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]):
910
yield (entry[1], entry[2])
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]
835
915
def _add_git_sha(self, hexsha, type, type_data):
836
916
if hexsha is not None:
837
917
self._name.update(hexsha)
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])
921
td += (type_data[2]["testament3-sha1"],)
842
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
926
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
844
928
# This object is not represented in Git - perhaps an empty
846
self._name.update(type + " ".join(type_data))
930
self._name.update(type + b" ".join(type_data))
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))
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":
943
verifiers = {"testament3-sha1": data[3]}
948
yield ("commit", (data[1], data[2], verifiers))
858
return (data[0], tuple(data[1:]))
950
yield (data[0].decode('ascii'), tuple(data[1:]))
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)):
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
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)):
879
971
formats = registry.Registry()
880
972
formats.register(TdbGitCacheFormat().get_format_string(),
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())
891
formats.register('default', SqliteGitCacheFormat())
893
formats.register('default', TdbGitCacheFormat())
979
formats.register('default', IndexGitCacheFormat())
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:
990
repo_transport.mkdir("git")
991
except bzr_errors.FileExists:
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"):
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"):
904
999
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
905
1000
repo_transport.rename("git.tdb", "git/idmap.tdb")
908
1003
def remove_readonly_transport_decorator(transport):
909
1004
if transport.is_readonly():
910
return transport._decorated
1006
return transport._decorated
1007
except AttributeError:
1008
raise bzr_errors.ReadOnlyError(transport)
911
1009
return transport