32
btree_index as _mod_btree_index,
40
btree_index as _mod_btree_index,
39
from bzrlib.transport import (
44
from ..transport import (
45
get_transport_from_path,
44
49
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")
50
path = os.path.join(bedding.cache_dir(), "git")
51
if not os.path.isdir(path):
56
def get_remote_cache_transport(repository):
57
"""Retrieve the transport to use when accessing (unwritable) remote
60
uuid = getattr(repository, "uuid", None)
62
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())
64
path = os.path.join(get_cache_dir(), uuid)
65
if not os.path.isdir(path):
67
return get_transport_from_path(path)
64
70
def check_pysqlite_version(sqlite3):
65
71
"""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)):
74
if (sqlite3.sqlite_version_info[0] < 3
75
or (sqlite3.sqlite_version_info[0] == 3 and
76
sqlite3.sqlite_version_info[1] < 3)):
71
77
trace.warning('Needs at least sqlite 3.3.x')
72
raise bzrlib.errors.BzrError("incompatible sqlite library")
78
raise bzr_errors.BzrError("incompatible sqlite library")
77
84
check_pysqlite_version(sqlite3)
78
except (ImportError, bzrlib.errors.BzrError), e:
85
except (ImportError, bzr_errors.BzrError):
79
86
from pysqlite2 import dbapi2 as sqlite3
80
87
check_pysqlite_version(sqlite3)
82
89
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
84
raise bzrlib.errors.BzrError("missing sqlite library")
91
raise bzr_errors.BzrError("missing sqlite library")
87
94
_mapdbs = threading.local()
89
98
"""Get a cache for this thread's db connections."""
197
211
"""Open a cache file for a repository.
199
213
This will use the repository's transport to store the cache file, or
200
use the users global cache directory if the repository has no
214
use the users global cache directory if the repository has no
201
215
transport associated with it.
203
217
:param repository: Repository to open the cache for
204
218
:return: A `BzrGitCache`
220
from ..transport.local import LocalTransport
206
221
repo_transport = getattr(repository, "_transport", None)
207
if repo_transport is not None:
208
# Even if we don't write to this repo, we should be able
222
if (repo_transport is not None
223
and isinstance(repo_transport, LocalTransport)):
224
# Even if we don't write to this repo, we should be able
209
225
# to update its cache.
210
repo_transport = remove_readonly_transport_decorator(repo_transport)
212
repo_transport.mkdir('git')
213
except bzrlib.errors.FileExists:
215
transport = repo_transport.clone('git')
227
repo_transport = remove_readonly_transport_decorator(
229
except bzr_errors.ReadOnlyError:
233
repo_transport.mkdir('git')
234
except bzr_errors.FileExists:
236
transport = repo_transport.clone('git')
217
transport = get_remote_cache_transport()
239
if transport is None:
240
transport = get_remote_cache_transport(repository)
218
241
return cls.from_transport(transport)
221
244
class CacheUpdater(object):
222
245
"""Base class for objects that can update a bzr-git cache."""
224
def add_object(self, obj, ie, path):
247
def add_object(self, obj, bzr_key_data, path):
225
248
"""Add an object.
227
250
:param obj: Object type ("commit", "blob" or "tree")
228
:param ie: Inventory entry (for blob/tree) or testament_sha in case
251
:param bzr_key_data: bzr key store data or testament_sha in case
230
253
:param path: Path of the object (optional)
263
286
self._commit = None
264
287
self._entries = []
266
def add_object(self, obj, ie, path):
267
if obj.type_name == "commit":
289
def add_object(self, obj, bzr_key_data, path):
290
if isinstance(obj, tuple):
291
(type_name, hexsha) = obj
293
type_name = obj.type_name.decode('ascii')
295
if not isinstance(hexsha, bytes):
296
raise TypeError(hexsha)
297
if type_name == "commit":
268
298
self._commit = obj
269
assert type(ie) is dict
270
type_data = (self.revid, self._commit.tree, ie)
271
self.cache.idmap._by_revid[self.revid] = obj.id
272
elif obj.type_name in ("blob", "tree"):
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
299
if type(bzr_key_data) is not dict:
300
raise TypeError(bzr_key_data)
302
type_data = (self.revid, self._commit.tree, bzr_key_data)
303
self.cache.idmap._by_revid[self.revid] = hexsha
304
elif type_name in ("blob", "tree"):
305
if bzr_key_data is not None:
306
key = type_data = bzr_key_data
307
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
308
type_data[0]] = hexsha
281
310
raise AssertionError
282
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
311
entry = (type_name, type_data)
312
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
284
314
def finish(self):
285
315
if self._commit is None:
329
def add_object(self, obj, ie, path):
330
if obj.type_name == "commit":
363
def add_object(self, obj, bzr_key_data, path):
364
if isinstance(obj, tuple):
365
(type_name, hexsha) = obj
367
type_name = obj.type_name.decode('ascii')
369
if not isinstance(hexsha, bytes):
370
raise TypeError(hexsha)
371
if type_name == "commit":
331
372
self._commit = obj
332
self._testament3_sha1 = ie["testament3-sha1"]
333
assert type(ie) is dict
334
elif obj.type_name == "tree":
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))
373
if type(bzr_key_data) is not dict:
374
raise TypeError(bzr_key_data)
375
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
376
elif type_name == "tree":
377
if bzr_key_data is not None:
378
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
379
elif type_name == "blob":
380
if bzr_key_data is not None:
381
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
341
383
raise AssertionError
350
392
"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))
395
"replace into commits (sha1, revid, tree_sha, testament3_sha1) "
396
"values (?, ?, ?, ?)",
397
(self._commit.id, self.revid, self._commit.tree,
398
self._testament3_sha1))
355
399
return self._commit
358
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
402
def SqliteBzrGitCache(p):
403
return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
361
406
class SqliteGitCacheFormat(BzrGitCacheFormat):
363
408
def get_format_string(self):
364
return 'bzr-git sha map version 1 using sqlite\n'
409
return b'bzr-git sha map version 1 using sqlite\n'
366
411
def open(self, transport):
368
413
basepath = transport.local_abspath(".")
369
except bzrlib.errors.NotLocalUrl:
414
except bzr_errors.NotLocalUrl:
370
415
basepath = get_cache_dir()
371
416
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
447
498
tree: fileid, revid
448
499
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()
502
cursor = self.db.execute(
503
"select revid, tree_sha, testament3_sha1 from commits where "
505
for row in cursor.fetchall():
507
if row[2] is not None:
508
verifiers = {"testament3-sha1": row[2]}
511
yield ("commit", (row[0], row[1], verifiers))
512
cursor = self.db.execute(
513
"select fileid, revid from blobs where sha1 = ?", (sha,))
514
for row in cursor.fetchall():
517
cursor = self.db.execute(
518
"select fileid, revid from trees where sha1 = ?", (sha,))
519
for row in cursor.fetchall():
461
525
def revids(self):
462
526
"""List the revision ids known."""
480
544
self._commit = None
481
545
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"])
547
def add_object(self, obj, bzr_key_data, path):
548
if isinstance(obj, tuple):
549
(type_name, hexsha) = obj
550
sha = hex_to_sha(hexsha)
552
type_name = obj.type_name.decode('ascii')
553
sha = obj.sha().digest()
554
if type_name == "commit":
555
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
556
if type(bzr_key_data) is not dict:
557
raise TypeError(bzr_key_data)
558
type_data = (self.revid, obj.tree)
560
type_data += (bzr_key_data["testament3-sha1"],)
489
563
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)
564
elif type_name == "blob":
565
if bzr_key_data is None:
568
(b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
569
type_data = bzr_key_data
570
elif type_name == "tree":
571
if bzr_key_data is None:
573
type_data = bzr_key_data
500
575
raise AssertionError
501
self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
576
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
579
oldval = self.db[key]
583
if not oldval.endswith(b'\n'):
584
self.db[key] = b"".join([oldval, b"\n", entry])
586
self.db[key] = b"".join([oldval, entry])
503
588
def finish(self):
504
589
if self._commit is None:
593
684
if len(sha) == 40:
594
685
sha = hex_to_sha(sha)
595
data = self.db["git\0" + sha].split("\0")
596
if data[0] == "commit":
598
return (data[0], (data[1], data[2], {}))
686
value = self.db[b"git\0" + sha]
687
for data in value.splitlines():
688
data = data.split(b"\0")
689
type_name = data[0].decode('ascii')
690
if type_name == "commit":
692
yield (type_name, (data[1], data[2], {}))
694
yield (type_name, (data[1], data[2],
695
{"testament3-sha1": data[3]}))
696
elif type_name in ("tree", "blob"):
697
yield (type_name, tuple(data[1:]))
600
return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
602
return (data[0], tuple(data[1:]))
699
raise AssertionError("unknown type %r" % type_name)
604
701
def missing_revisions(self, revids):
606
703
for revid in revids:
607
if self.db.get("commit\0" + revid) is None:
704
if self.db.get(b"commit\0" + revid) is None:
709
return self.db.keys()
611
711
def revids(self):
612
712
"""List the revision ids known."""
613
for key in self.db.iterkeys():
614
if key.startswith("commit\0"):
713
for key in self._keys():
714
if key.startswith(b"commit\0"):
618
718
"""List the SHA1s."""
619
for key in self.db.iterkeys():
620
if key.startswith("git\0"):
719
for key in self._keys():
720
if key.startswith(b"git\0"):
621
721
yield sha_to_hex(key[4:])
629
729
def add(self, obj):
630
730
self._vf.insert_record_stream(
631
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
632
obj.as_legacy_object_chunks())])
731
[versionedfile.ChunkedContentFactory(
732
(obj.id,), [], None, obj.as_legacy_object_chunks())])
634
734
def __getitem__(self, sha):
635
735
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
636
entry = stream.next()
637
737
if entry.storage_kind == 'absent':
638
738
raise KeyError(sha)
639
739
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
742
class IndexCacheUpdater(CacheUpdater):
659
744
def __init__(self, cache, rev):
662
747
self.parent_revids = rev.parent_ids
663
748
self._commit = None
664
749
self._entries = []
665
self._cache_objs = set()
667
def add_object(self, obj, ie, path):
668
if obj.type_name == "commit":
751
def add_object(self, obj, bzr_key_data, path):
752
if isinstance(obj, tuple):
753
(type_name, hexsha) = obj
755
type_name = obj.type_name.decode('ascii')
757
if type_name == "commit":
669
758
self._commit = obj
670
assert type(ie) is dict
671
self.cache.idmap._add_git_sha(obj.id, "commit",
672
(self.revid, obj.tree, ie))
673
self.cache.idmap._add_node(("commit", self.revid, "X"),
674
" ".join((obj.id, obj.tree)))
675
self._cache_objs.add((obj, path))
676
elif obj.type_name == "blob":
677
self.cache.idmap._add_git_sha(obj.id, "blob",
678
(ie.file_id, ie.revision))
679
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
680
if ie.kind == "symlink":
681
self._cache_objs.add((obj, path))
682
elif obj.type_name == "tree":
683
self.cache.idmap._add_git_sha(obj.id, "tree",
684
(ie.file_id, self.revid))
685
self._cache_objs.add((obj, path))
759
if type(bzr_key_data) is not dict:
760
raise TypeError(bzr_key_data)
761
self.cache.idmap._add_git_sha(hexsha, b"commit",
762
(self.revid, obj.tree, bzr_key_data))
763
self.cache.idmap._add_node((b"commit", self.revid, b"X"),
764
b" ".join((hexsha, obj.tree)))
765
elif type_name == "blob":
766
self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
767
self.cache.idmap._add_node((b"blob", bzr_key_data[0],
768
bzr_key_data[1]), hexsha)
769
elif type_name == "tree":
770
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
687
772
raise AssertionError
689
774
def finish(self):
690
self.cache.content_cache.add_multi(self._cache_objs)
691
775
return self._commit
694
778
class IndexBzrGitCache(BzrGitCache):
696
780
def __init__(self, transport=None):
697
mapper = versionedfile.ConstantMapper("trees")
698
781
shamap = IndexGitShaMap(transport.clone('index'))
699
#trees_store = knit.make_file_factory(True, mapper)(transport)
700
#content_cache = VersionedFilesContentCache(trees_store)
701
from bzrlib.plugins.git.transportgit import TransportObjectStore
702
store = TransportObjectStore(transport.clone('objects'))
703
content_cache = GitObjectStoreContentCache(store)
704
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
782
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
708
785
class IndexGitCacheFormat(BzrGitCacheFormat):
710
787
def get_format_string(self):
711
return 'bzr-git sha map with git object cache version 1\n'
788
return b'bzr-git sha map with git object cache version 1\n'
713
790
def initialize(self, transport):
714
791
super(IndexGitCacheFormat, self).initialize(transport)
715
792
transport.mkdir('index')
716
793
transport.mkdir('objects')
717
from bzrlib.plugins.git.transportgit import TransportObjectStore
794
from .transportgit import TransportObjectStore
718
795
TransportObjectStore.init(transport.clone('objects'))
720
797
def open(self, transport):
796
877
self._name = None
798
879
def abort_write_group(self):
799
assert self._builder is not None
880
if self._builder is None:
881
raise bzr_errors.BzrError('builder not open')
800
882
self._builder = None
801
883
self._name = None
803
885
def _add_node(self, key, value):
805
889
self._builder.add_node(key, value)
806
except bzrlib.errors.BadIndexDuplicateKey:
807
# Multiple bzr objects can have the same contents
812
894
def _get_entry(self, key):
813
895
entries = self._index.iter_entries([key])
815
return entries.next()[2]
897
return next(entries)[2]
816
898
except StopIteration:
817
899
if self._builder is None:
819
901
entries = self._builder.iter_entries([key])
821
return entries.next()[2]
903
return next(entries)[2]
822
904
except StopIteration:
825
def _iter_keys_prefix(self, prefix):
907
def _iter_entries_prefix(self, prefix):
826
908
for entry in self._index.iter_entries_prefix([prefix]):
909
yield (entry[1], entry[2])
828
910
if self._builder is not None:
829
911
for entry in self._builder.iter_entries_prefix([prefix]):
912
yield (entry[1], entry[2])
832
914
def lookup_commit(self, revid):
833
return self._get_entry(("commit", revid, "X"))[:40]
915
return self._get_entry((b"commit", revid, b"X"))[:40]
835
917
def _add_git_sha(self, hexsha, type, type_data):
836
918
if hexsha is not None:
837
919
self._name.update(hexsha)
839
td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
920
if type == b"commit":
921
td = (type_data[0], type_data[1])
923
td += (type_data[2]["testament3-sha1"],)
842
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
928
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
844
930
# This object is not represented in Git - perhaps an empty
846
self._name.update(type + " ".join(type_data))
932
self._name.update(type + b" ".join(type_data))
848
934
def lookup_blob_id(self, fileid, revision):
849
return self._get_entry(("blob", fileid, revision))
935
return self._get_entry((b"blob", fileid, revision))
851
937
def lookup_git_sha(self, sha):
852
938
if len(sha) == 20:
853
939
sha = sha_to_hex(sha)
854
data = self._get_entry(("git", sha, "X")).split(" ", 3)
855
if data[0] == "commit":
856
return ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
940
value = self._get_entry((b"git", sha, b"X"))
941
data = value.split(b" ", 3)
942
if data[0] == b"commit":
945
verifiers = {"testament3-sha1": data[3]}
950
yield ("commit", (data[1], data[2], verifiers))
858
return (data[0], tuple(data[1:]))
952
yield (data[0].decode('ascii'), tuple(data[1:]))
860
954
def revids(self):
861
955
"""List the revision ids known."""
862
for key in self._iter_keys_prefix(("commit", None, None)):
956
for key, value in self._iter_entries_prefix((b"commit", None, None)):
865
959
def missing_revisions(self, revids):
866
960
"""Return set of all the revisions that are not present."""
867
961
missing_revids = set(revids)
868
962
for _, key, value in self._index.iter_entries((
869
("commit", revid, "X") for revid in revids)):
963
(b"commit", revid, b"X") for revid in revids)):
870
964
missing_revids.remove(key[1])
871
965
return missing_revids
874
968
"""List the SHA1s."""
875
for key in self._iter_keys_prefix(("git", None, None)):
969
for key, value in self._iter_entries_prefix((b"git", None, None)):
879
973
formats = registry.Registry()
880
974
formats.register(TdbGitCacheFormat().get_format_string(),
882
976
formats.register(SqliteGitCacheFormat().get_format_string(),
883
SqliteGitCacheFormat())
977
SqliteGitCacheFormat())
884
978
formats.register(IndexGitCacheFormat().get_format_string(),
885
IndexGitCacheFormat())
979
IndexGitCacheFormat())
886
980
# In the future, this will become the default:
887
# formats.register('default', IndexGitCacheFormat())
891
formats.register('default', SqliteGitCacheFormat())
893
formats.register('default', TdbGitCacheFormat())
981
formats.register('default', IndexGitCacheFormat())
897
984
def migrate_ancient_formats(repo_transport):
898
# Prefer migrating git.db over git.tdb, since the latter may not
985
# Migrate older cache formats
986
repo_transport = remove_readonly_transport_decorator(repo_transport)
987
has_sqlite = repo_transport.has("git.db")
988
has_tdb = repo_transport.has("git.tdb")
989
if not has_sqlite or has_tdb:
992
repo_transport.mkdir("git")
993
except bzr_errors.FileExists:
995
# Prefer migrating git.db over git.tdb, since the latter may not
899
996
# be openable on some platforms.
900
if repo_transport.has("git.db"):
901
998
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
902
999
repo_transport.rename("git.db", "git/idmap.db")
903
elif repo_transport.has("git.tdb"):
904
1001
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
905
1002
repo_transport.rename("git.tdb", "git/idmap.tdb")
908
1005
def remove_readonly_transport_decorator(transport):
909
1006
if transport.is_readonly():
910
return transport._decorated
1008
return transport._decorated
1009
except AttributeError:
1010
raise bzr_errors.ReadOnlyError(transport)
911
1011
return transport