46
55
from xdg.BaseDirectory import xdg_cache_home
47
56
except ImportError:
48
from bzrlib.config import config_dir
57
from ..config import config_dir
49
58
ret = os.path.join(config_dir(), "git")
51
ret = os.path.join(xdg_cache_home, "bazaar", "git")
60
ret = os.path.join(xdg_cache_home, "breezy", "git")
52
61
if not os.path.isdir(ret):
57
def get_remote_cache_transport():
58
"""Retrieve the transport to use when accessing (unwritable) remote
66
def get_remote_cache_transport(repository):
67
"""Retrieve the transport to use when accessing (unwritable) remote
61
return get_transport(get_cache_dir())
70
uuid = getattr(repository, "uuid", None)
72
path = get_cache_dir()
74
path = os.path.join(get_cache_dir(), uuid)
75
if not os.path.isdir(path):
77
return get_transport(path)
64
80
def check_pysqlite_version(sqlite3):
65
81
"""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)):
84
if (sqlite3.sqlite_version_info[0] < 3
85
or (sqlite3.sqlite_version_info[0] == 3 and
86
sqlite3.sqlite_version_info[1] < 3)):
71
87
trace.warning('Needs at least sqlite 3.3.x')
72
raise bzrlib.errors.BzrError("incompatible sqlite library")
88
raise bzr_errors.BzrError("incompatible sqlite library")
77
94
check_pysqlite_version(sqlite3)
78
except (ImportError, bzrlib.errors.BzrError), e:
95
except (ImportError, bzr_errors.BzrError):
79
96
from pysqlite2 import dbapi2 as sqlite3
80
97
check_pysqlite_version(sqlite3)
82
99
trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
84
raise bzrlib.errors.BzrError("missing sqlite library")
101
raise bzr_errors.BzrError("missing sqlite library")
87
104
_mapdbs = threading.local()
89
108
"""Get a cache for this thread's db connections."""
197
221
"""Open a cache file for a repository.
199
223
This will use the repository's transport to store the cache file, or
200
use the users global cache directory if the repository has no
224
use the users global cache directory if the repository has no
201
225
transport associated with it.
203
227
:param repository: Repository to open the cache for
204
228
:return: A `BzrGitCache`
230
from ..transport.local import LocalTransport
206
231
repo_transport = getattr(repository, "_transport", None)
207
if repo_transport is not None:
208
# Even if we don't write to this repo, we should be able
232
if (repo_transport is not None
233
and isinstance(repo_transport, LocalTransport)):
234
# Even if we don't write to this repo, we should be able
209
235
# to update its cache.
210
repo_transport = remove_readonly_transport_decorator(repo_transport)
212
repo_transport.mkdir('git')
213
except bzrlib.errors.FileExists:
215
transport = repo_transport.clone('git')
237
repo_transport = remove_readonly_transport_decorator(
239
except bzr_errors.ReadOnlyError:
243
repo_transport.mkdir('git')
244
except bzr_errors.FileExists:
246
transport = repo_transport.clone('git')
217
transport = get_remote_cache_transport()
249
if transport is None:
250
transport = get_remote_cache_transport(repository)
218
251
return cls.from_transport(transport)
221
254
class CacheUpdater(object):
222
255
"""Base class for objects that can update a bzr-git cache."""
224
def add_object(self, obj, ie, path):
257
def add_object(self, obj, bzr_key_data, path):
225
258
"""Add an object.
227
260
:param obj: Object type ("commit", "blob" or "tree")
228
:param ie: Inventory entry (for blob/tree) or testament_sha in case
261
:param bzr_key_data: bzr key store data or testament_sha in case
230
263
:param path: Path of the object (optional)
263
296
self._commit = None
264
297
self._entries = []
266
def add_object(self, obj, ie, path):
267
if obj.type_name == "commit":
299
def add_object(self, obj, bzr_key_data, path):
300
if isinstance(obj, tuple):
301
(type_name, hexsha) = obj
303
type_name = obj.type_name.decode('ascii')
305
if not isinstance(hexsha, bytes):
306
raise TypeError(hexsha)
307
if type_name == "commit":
268
308
self._commit = obj
269
assert type(ie) is dict
270
type_data = (self.revid, self._commit.tree, ie)
271
self.cache.idmap._by_revid[self.revid] = obj.id
272
elif obj.type_name in ("blob", "tree"):
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
309
if type(bzr_key_data) is not dict:
310
raise TypeError(bzr_key_data)
312
type_data = (self.revid, self._commit.tree, bzr_key_data)
313
self.cache.idmap._by_revid[self.revid] = hexsha
314
elif type_name in ("blob", "tree"):
315
if bzr_key_data is not None:
316
key = type_data = bzr_key_data
317
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[
318
type_data[0]] = hexsha
281
320
raise AssertionError
282
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
321
entry = (type_name, type_data)
322
self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
284
324
def finish(self):
285
325
if self._commit is None:
329
def add_object(self, obj, ie, path):
330
if obj.type_name == "commit":
373
def add_object(self, obj, bzr_key_data, path):
374
if isinstance(obj, tuple):
375
(type_name, hexsha) = obj
377
type_name = obj.type_name.decode('ascii')
379
if not isinstance(hexsha, bytes):
380
raise TypeError(hexsha)
381
if type_name == "commit":
331
382
self._commit = obj
332
self._testament3_sha1 = ie["testament3-sha1"]
333
assert type(ie) is dict
334
elif obj.type_name == "tree":
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))
383
if type(bzr_key_data) is not dict:
384
raise TypeError(bzr_key_data)
385
self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
386
elif type_name == "tree":
387
if bzr_key_data is not None:
388
self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
389
elif type_name == "blob":
390
if bzr_key_data is not None:
391
self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
341
393
raise AssertionError
350
402
"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))
405
"replace into commits (sha1, revid, tree_sha, testament3_sha1) "
406
"values (?, ?, ?, ?)",
407
(self._commit.id, self.revid, self._commit.tree,
408
self._testament3_sha1))
355
409
return self._commit
358
SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), None, SqliteCacheUpdater)
412
def SqliteBzrGitCache(p):
413
return BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
361
416
class SqliteGitCacheFormat(BzrGitCacheFormat):
363
418
def get_format_string(self):
364
return 'bzr-git sha map version 1 using sqlite\n'
419
return b'bzr-git sha map version 1 using sqlite\n'
366
421
def open(self, transport):
368
423
basepath = transport.local_abspath(".")
369
except bzrlib.errors.NotLocalUrl:
424
except bzr_errors.NotLocalUrl:
370
425
basepath = get_cache_dir()
371
426
return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
447
508
tree: fileid, revid
448
509
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()
512
cursor = self.db.execute(
513
"select revid, tree_sha, testament3_sha1 from commits where "
515
for row in cursor.fetchall():
517
if row[2] is not None:
518
verifiers = {"testament3-sha1": row[2]}
521
yield ("commit", (row[0], row[1], verifiers))
522
cursor = self.db.execute(
523
"select fileid, revid from blobs where sha1 = ?", (sha,))
524
for row in cursor.fetchall():
527
cursor = self.db.execute(
528
"select fileid, revid from trees where sha1 = ?", (sha,))
529
for row in cursor.fetchall():
461
535
def revids(self):
462
536
"""List the revision ids known."""
480
554
self._commit = None
481
555
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"])
557
def add_object(self, obj, bzr_key_data, path):
558
if isinstance(obj, tuple):
559
(type_name, hexsha) = obj
560
sha = hex_to_sha(hexsha)
562
type_name = obj.type_name.decode('ascii')
563
sha = obj.sha().digest()
564
if type_name == "commit":
565
self.db[b"commit\0" + self.revid] = b"\0".join((sha, obj.tree))
566
if type(bzr_key_data) is not dict:
567
raise TypeError(bzr_key_data)
568
type_data = (self.revid, obj.tree)
570
type_data += (bzr_key_data["testament3-sha1"],)
489
573
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)
574
elif type_name == "blob":
575
if bzr_key_data is None:
578
(b"blob", bzr_key_data[0], bzr_key_data[1]))] = sha
579
type_data = bzr_key_data
580
elif type_name == "tree":
581
if bzr_key_data is None:
583
type_data = bzr_key_data
500
585
raise AssertionError
501
self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
586
entry = b"\0".join((type_name.encode('ascii'), ) + type_data) + b"\n"
589
oldval = self.db[key]
593
if not oldval.endswith(b'\n'):
594
self.db[key] = b"".join([oldval, b"\n", entry])
596
self.db[key] = b"".join([oldval, entry])
503
598
def finish(self):
504
599
if self._commit is None:
593
694
if len(sha) == 40:
594
695
sha = hex_to_sha(sha)
595
data = self.db["git\0" + sha].split("\0")
596
if data[0] == "commit":
598
return (data[0], (data[1], data[2], {}))
696
value = self.db[b"git\0" + sha]
697
for data in value.splitlines():
698
data = data.split(b"\0")
699
type_name = data[0].decode('ascii')
700
if type_name == "commit":
702
yield (type_name, (data[1], data[2], {}))
704
yield (type_name, (data[1], data[2],
705
{"testament3-sha1": data[3]}))
706
elif type_name in ("tree", "blob"):
707
yield (type_name, tuple(data[1:]))
600
return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
602
return (data[0], tuple(data[1:]))
709
raise AssertionError("unknown type %r" % type_name)
604
711
def missing_revisions(self, revids):
606
713
for revid in revids:
607
if self.db.get("commit\0" + revid) is None:
714
if self.db.get(b"commit\0" + revid) is None:
720
return self.db.keys()
721
except AttributeError: # python < 3
722
return self.db.iterkeys()
611
724
def revids(self):
612
725
"""List the revision ids known."""
613
for key in self.db.iterkeys():
614
if key.startswith("commit\0"):
726
for key in self._keys():
727
if key.startswith(b"commit\0"):
618
731
"""List the SHA1s."""
619
for key in self.db.iterkeys():
620
if key.startswith("git\0"):
732
for key in self._keys():
733
if key.startswith(b"git\0"):
621
734
yield sha_to_hex(key[4:])
629
742
def add(self, obj):
630
743
self._vf.insert_record_stream(
631
[versionedfile.ChunkedContentFactory((obj.id,), [], None,
632
obj.as_legacy_object_chunks())])
744
[versionedfile.ChunkedContentFactory(
745
(obj.id,), [], None, obj.as_legacy_object_chunks())])
634
747
def __getitem__(self, sha):
635
748
stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
636
entry = stream.next()
637
750
if entry.storage_kind == 'absent':
638
751
raise KeyError(sha)
639
752
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
755
class IndexCacheUpdater(CacheUpdater):
659
757
def __init__(self, cache, rev):
662
760
self.parent_revids = rev.parent_ids
663
761
self._commit = None
664
762
self._entries = []
665
self._cache_objs = set()
667
def add_object(self, obj, ie, path):
668
if obj.type_name == "commit":
764
def add_object(self, obj, bzr_key_data, path):
765
if isinstance(obj, tuple):
766
(type_name, hexsha) = obj
768
type_name = obj.type_name.decode('ascii')
770
if type_name == "commit":
669
771
self._commit = obj
670
assert type(ie) is dict
671
self.cache.idmap._add_git_sha(obj.id, "commit",
672
(self.revid, obj.tree, ie))
673
self.cache.idmap._add_node(("commit", self.revid, "X"),
674
" ".join((obj.id, obj.tree)))
675
self._cache_objs.add((obj, path))
676
elif obj.type_name == "blob":
677
self.cache.idmap._add_git_sha(obj.id, "blob",
678
(ie.file_id, ie.revision))
679
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
680
if ie.kind == "symlink":
681
self._cache_objs.add((obj, path))
682
elif obj.type_name == "tree":
683
self.cache.idmap._add_git_sha(obj.id, "tree",
684
(ie.file_id, self.revid))
685
self._cache_objs.add((obj, path))
772
if type(bzr_key_data) is not dict:
773
raise TypeError(bzr_key_data)
774
self.cache.idmap._add_git_sha(hexsha, b"commit",
775
(self.revid, obj.tree, bzr_key_data))
776
self.cache.idmap._add_node((b"commit", self.revid, b"X"),
777
b" ".join((hexsha, obj.tree)))
778
elif type_name == "blob":
779
self.cache.idmap._add_git_sha(hexsha, b"blob", bzr_key_data)
780
self.cache.idmap._add_node((b"blob", bzr_key_data[0],
781
bzr_key_data[1]), hexsha)
782
elif type_name == "tree":
783
self.cache.idmap._add_git_sha(hexsha, b"tree", bzr_key_data)
687
785
raise AssertionError
689
787
def finish(self):
690
self.cache.content_cache.add_multi(self._cache_objs)
691
788
return self._commit
694
791
class IndexBzrGitCache(BzrGitCache):
696
793
def __init__(self, transport=None):
697
mapper = versionedfile.ConstantMapper("trees")
698
794
shamap = IndexGitShaMap(transport.clone('index'))
699
#trees_store = knit.make_file_factory(True, mapper)(transport)
700
#content_cache = VersionedFilesContentCache(trees_store)
701
from bzrlib.plugins.git.transportgit import TransportObjectStore
702
store = TransportObjectStore(transport.clone('objects'))
703
content_cache = GitObjectStoreContentCache(store)
704
super(IndexBzrGitCache, self).__init__(shamap, content_cache,
795
super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
708
798
class IndexGitCacheFormat(BzrGitCacheFormat):
710
800
def get_format_string(self):
711
return 'bzr-git sha map with git object cache version 1\n'
801
return b'bzr-git sha map with git object cache version 1\n'
713
803
def initialize(self, transport):
714
804
super(IndexGitCacheFormat, self).initialize(transport)
715
805
transport.mkdir('index')
716
806
transport.mkdir('objects')
717
from bzrlib.plugins.git.transportgit import TransportObjectStore
807
from .transportgit import TransportObjectStore
718
808
TransportObjectStore.init(transport.clone('objects'))
720
810
def open(self, transport):
796
891
self._name = None
798
893
def abort_write_group(self):
799
assert self._builder is not None
894
if self._builder is None:
895
raise bzr_errors.BzrError('builder not open')
800
896
self._builder = None
801
897
self._name = None
803
899
def _add_node(self, key, value):
805
903
self._builder.add_node(key, value)
806
except bzrlib.errors.BadIndexDuplicateKey:
807
# Multiple bzr objects can have the same contents
812
908
def _get_entry(self, key):
813
909
entries = self._index.iter_entries([key])
815
return entries.next()[2]
911
return next(entries)[2]
816
912
except StopIteration:
817
913
if self._builder is None:
819
915
entries = self._builder.iter_entries([key])
821
return entries.next()[2]
917
return next(entries)[2]
822
918
except StopIteration:
825
def _iter_keys_prefix(self, prefix):
921
def _iter_entries_prefix(self, prefix):
826
922
for entry in self._index.iter_entries_prefix([prefix]):
923
yield (entry[1], entry[2])
828
924
if self._builder is not None:
829
925
for entry in self._builder.iter_entries_prefix([prefix]):
926
yield (entry[1], entry[2])
832
928
def lookup_commit(self, revid):
833
return self._get_entry(("commit", revid, "X"))[:40]
929
return self._get_entry((b"commit", revid, b"X"))[:40]
835
931
def _add_git_sha(self, hexsha, type, type_data):
836
932
if hexsha is not None:
837
933
self._name.update(hexsha)
839
td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
934
if type == b"commit":
935
td = (type_data[0], type_data[1])
937
td += (type_data[2]["testament3-sha1"],)
842
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
942
self._add_node((b"git", hexsha, b"X"), b" ".join((type,) + td))
844
944
# This object is not represented in Git - perhaps an empty
846
self._name.update(type + " ".join(type_data))
946
self._name.update(type + b" ".join(type_data))
848
948
def lookup_blob_id(self, fileid, revision):
849
return self._get_entry(("blob", fileid, revision))
949
return self._get_entry((b"blob", fileid, revision))
851
951
def lookup_git_sha(self, sha):
852
952
if len(sha) == 20:
853
953
sha = sha_to_hex(sha)
854
data = self._get_entry(("git", sha, "X")).split(" ", 3)
855
if data[0] == "commit":
856
return ("commit", (data[1], data[2], {"testament3-sha1": data[3]}))
954
value = self._get_entry((b"git", sha, b"X"))
955
data = value.split(b" ", 3)
956
if data[0] == b"commit":
959
verifiers = {"testament3-sha1": data[3]}
964
yield ("commit", (data[1], data[2], verifiers))
858
return (data[0], tuple(data[1:]))
966
yield (data[0].decode('ascii'), tuple(data[1:]))
860
968
def revids(self):
861
969
"""List the revision ids known."""
862
for key in self._iter_keys_prefix(("commit", None, None)):
970
for key, value in self._iter_entries_prefix((b"commit", None, None)):
865
973
def missing_revisions(self, revids):
866
974
"""Return set of all the revisions that are not present."""
867
975
missing_revids = set(revids)
868
976
for _, key, value in self._index.iter_entries((
869
("commit", revid, "X") for revid in revids)):
977
(b"commit", revid, b"X") for revid in revids)):
870
978
missing_revids.remove(key[1])
871
979
return missing_revids
874
982
"""List the SHA1s."""
875
for key in self._iter_keys_prefix(("git", None, None)):
983
for key, value in self._iter_entries_prefix((b"git", None, None)):
879
987
formats = registry.Registry()
880
988
formats.register(TdbGitCacheFormat().get_format_string(),
882
990
formats.register(SqliteGitCacheFormat().get_format_string(),
883
SqliteGitCacheFormat())
991
SqliteGitCacheFormat())
884
992
formats.register(IndexGitCacheFormat().get_format_string(),
885
IndexGitCacheFormat())
993
IndexGitCacheFormat())
886
994
# In the future, this will become the default:
887
# formats.register('default', IndexGitCacheFormat())
891
formats.register('default', SqliteGitCacheFormat())
893
formats.register('default', TdbGitCacheFormat())
995
formats.register('default', IndexGitCacheFormat())
897
998
def migrate_ancient_formats(repo_transport):
898
# Prefer migrating git.db over git.tdb, since the latter may not
999
# Migrate older cache formats
1000
repo_transport = remove_readonly_transport_decorator(repo_transport)
1001
has_sqlite = repo_transport.has("git.db")
1002
has_tdb = repo_transport.has("git.tdb")
1003
if not has_sqlite or has_tdb:
1006
repo_transport.mkdir("git")
1007
except bzr_errors.FileExists:
1009
# Prefer migrating git.db over git.tdb, since the latter may not
899
1010
# be openable on some platforms.
900
if repo_transport.has("git.db"):
901
1012
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
902
1013
repo_transport.rename("git.db", "git/idmap.db")
903
elif repo_transport.has("git.tdb"):
904
1015
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
905
1016
repo_transport.rename("git.tdb", "git/idmap.tdb")
908
1019
def remove_readonly_transport_decorator(transport):
909
1020
if transport.is_readonly():
910
return transport._decorated
1022
return transport._decorated
1023
except AttributeError:
1024
raise bzr_errors.ReadOnlyError(transport)
911
1025
return transport