101
99
"""Lookup a Git sha in the database.
102
100
:param sha: Git object sha
103
101
:return: (type, type_data) with type_data:
104
commit: revid, tree_sha, verifiers
102
revision: revid, tree sha
108
104
raise NotImplementedError(self.lookup_git_sha)
148
144
class ContentCache(object):
149
145
"""Object that can cache Git objects."""
151
def add(self, object):
153
raise NotImplementedError(self.add)
155
def add_multi(self, objects):
156
"""Add multiple objects."""
160
147
def __getitem__(self, sha):
161
148
"""Retrieve an item, by SHA."""
162
149
raise NotImplementedError(self.__getitem__)
165
152
class BzrGitCacheFormat(object):
166
"""Bazaar-Git Cache Format."""
168
154
def get_format_string(self):
169
"""Return a single-line unique format string for this cache format."""
170
155
raise NotImplementedError(self.get_format_string)
172
157
def open(self, transport):
173
"""Open this format on a transport."""
174
158
raise NotImplementedError(self.open)
176
160
def initialize(self, transport):
177
"""Create a new instance of this cache format at transport."""
178
161
transport.put_bytes('format', self.get_format_string())
181
def from_transport(self, transport):
182
"""Open a cache file present on a transport, or initialize one.
184
:param transport: Transport to use
185
:return: A BzrGitCache instance
164
def from_repository(self, repository):
165
repo_transport = getattr(repository, "_transport", None)
166
if repo_transport is not None:
168
repo_transport.mkdir('git')
169
except bzrlib.errors.FileExists:
171
transport = repo_transport.clone('git')
173
transport = get_remote_cache_transport()
188
175
format_name = transport.get_bytes('format')
189
176
format = formats.get(format_name)
192
179
format.initialize(transport)
193
180
return format.open(transport)
196
def from_repository(cls, repository):
197
"""Open a cache file for a repository.
199
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
201
transport associated with it.
203
:param repository: Repository to open the cache for
204
:return: A `BzrGitCache`
206
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
209
# 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')
217
transport = get_remote_cache_transport()
218
return cls.from_transport(transport)
221
183
class CacheUpdater(object):
222
"""Base class for objects that can update a bzr-git cache."""
224
def add_object(self, obj, ie, path):
227
:param obj: Object type ("commit", "blob" or "tree")
228
:param ie: Inventory entry (for blob/tree) or testament_sha in case
230
:param path: Path of the object (optional)
185
def add_object(self, obj, ie):
232
186
raise NotImplementedError(self.add_object)
234
188
def finish(self):
244
198
self._cache_updater_klass = cache_updater_klass
246
200
def get_updater(self, rev):
247
"""Update an object that implements the CacheUpdater interface for
250
201
return self._cache_updater_klass(self, rev)
263
213
self._commit = None
264
214
self._entries = []
266
def add_object(self, obj, ie, path):
216
def add_object(self, obj, ie):
267
217
if obj.type_name == "commit":
268
218
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
220
type_data = (self.revid, self._commit.tree)
272
221
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
222
if obj.type_name == "blob":
223
revision = ie.revision
225
revision = self.revid
226
type_data = (ie.file_id, revision)
227
self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = obj.id
281
229
raise AssertionError
282
230
self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
290
238
class DictGitShaMap(GitShaMap):
291
"""Git SHA map that uses a dictionary."""
293
240
def __init__(self):
294
241
self._by_sha = {}
295
242
self._by_fileid = {}
298
244
def lookup_blob_id(self, fileid, revision):
299
245
return self._by_fileid[revision][fileid]
302
248
return self._by_sha[sha]
304
250
def lookup_tree_id(self, fileid, revision):
305
return self._by_fileid[revision][fileid]
307
def lookup_commit(self, revid):
308
return self._by_revid[revid]
251
return self._base._by_fileid[revision][fileid]
310
253
def revids(self):
311
254
for key, (type, type_data) in self._by_sha.iteritems():
329
def add_object(self, obj, ie, path):
272
def add_object(self, obj, ie):
330
273
if obj.type_name == "commit":
331
274
self._commit = obj
332
self._testament3_sha1 = ie["testament3-sha1"]
333
assert type(ie) is dict
334
276
elif obj.type_name == "tree":
336
self._trees.append((obj.id, ie.file_id, self.revid))
277
self._trees.append((obj.id, ie.file_id, self.revid))
337
278
elif obj.type_name == "blob":
339
self._blobs.append((obj.id, ie.file_id, ie.revision))
279
self._blobs.append((obj.id, ie.file_id, ie.revision))
341
281
raise AssertionError
350
290
"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))
293
"replace into commits (sha1, revid, tree_sha) values (?, ?, ?)",
294
(self._commit.id, self.revid, self._commit.tree))
355
295
return self._commit
406
345
create unique index if not exists trees_sha1 on trees(sha1);
407
346
create unique index if not exists trees_fileid_revid on trees(fileid, revid);
410
self.db.executescript(
411
"ALTER TABLE commits ADD testament3_sha1 TEXT;")
412
except sqlite3.OperationalError:
413
pass # Column already exists.
415
349
def __repr__(self):
416
350
return "%s(%r)" % (self.__class__.__name__, self.path)
418
352
def lookup_commit(self, revid):
419
cursor = self.db.execute("select sha1 from commits where revid = ?",
421
row = cursor.fetchone()
353
row = self.db.execute("select sha1 from commits where revid = ?", (revid,)).fetchone()
422
354
if row is not None:
433
365
raise KeyError(fileid)
435
367
def lookup_tree_id(self, fileid, revision):
436
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
368
row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, self.revid)).fetchone()
437
369
if row is not None:
439
371
raise KeyError(fileid)
444
376
:param sha: Git object sha
445
377
:return: (type, type_data) with type_data:
446
commit: revid, tree sha, verifiers
378
revision: revid, tree sha
450
row = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,)).fetchone()
380
row = self.db.execute("select revid, tree_sha from commits where sha1 = ?", (sha,)).fetchone()
451
381
if row is not None:
452
return ("commit", (row[0], row[1], {"testament3-sha1": row[2]}))
382
return ("commit", row)
453
383
row = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,)).fetchone()
454
384
if row is not None:
455
385
return ("blob", row)
480
409
self._commit = None
481
410
self._entries = []
483
def add_object(self, obj, ie, path):
412
def add_object(self, obj, ie):
484
413
sha = obj.sha().digest()
485
414
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"])
415
self.db["commit\0" + self.revid] = "\0".join((obj.id, obj.tree))
416
type_data = (self.revid, obj.tree)
489
417
self._commit = obj
490
419
elif obj.type_name == "blob":
493
420
self.db["\0".join(("blob", ie.file_id, ie.revision))] = sha
494
421
type_data = (ie.file_id, ie.revision)
495
422
elif obj.type_name == "tree":
498
423
type_data = (ie.file_id, self.revid)
500
425
raise AssertionError
501
self.db["git\0" + sha] = "\0".join((obj.type_name, ) + type_data)
426
self.db["git\0" + sha] = "\0".join((obj.type_name,
427
type_data[0], type_data[1]))
503
429
def finish(self):
504
430
if self._commit is None:
509
435
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
511
437
class TdbGitCacheFormat(BzrGitCacheFormat):
512
"""Cache format for tdb-based caches."""
514
439
def get_format_string(self):
515
440
return 'bzr-git sha map version 3 using tdb\n'
581
506
def lookup_blob_id(self, fileid, revision):
582
507
return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
584
509
def lookup_git_sha(self, sha):
585
510
"""Lookup a Git sha in the database.
587
512
:param sha: Git object sha
588
513
:return: (type, type_data) with type_data:
589
commit: revid, tree sha
514
revision: revid, tree sha
593
516
if len(sha) == 40:
594
517
sha = hex_to_sha(sha)
595
518
data = self.db["git\0" + sha].split("\0")
596
if data[0] == "commit":
598
return (data[0], (data[1], data[2], {}))
600
return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
602
return (data[0], tuple(data[1:]))
519
return (data[0], (data[1], data[2]))
604
521
def missing_revisions(self, revids):
639
556
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
559
class IndexCacheUpdater(CacheUpdater):
659
561
def __init__(self, cache, rev):
662
564
self.parent_revids = rev.parent_ids
663
565
self._commit = None
664
566
self._entries = []
665
self._cache_objs = set()
667
def add_object(self, obj, ie, path):
568
def add_object(self, obj, ie):
668
569
if obj.type_name == "commit":
669
570
self._commit = obj
670
assert type(ie) is dict
671
572
self.cache.idmap._add_git_sha(obj.id, "commit",
672
(self.revid, obj.tree, ie))
573
(self.revid, obj.tree))
673
574
self.cache.idmap._add_node(("commit", self.revid, "X"),
674
575
" ".join((obj.id, obj.tree)))
675
self._cache_objs.add((obj, path))
676
576
elif obj.type_name == "blob":
677
577
self.cache.idmap._add_git_sha(obj.id, "blob",
678
578
(ie.file_id, ie.revision))
679
579
self.cache.idmap._add_node(("blob", ie.file_id, ie.revision), obj.id)
680
580
if ie.kind == "symlink":
681
self._cache_objs.add((obj, path))
581
self.cache.content_cache.add(obj)
682
582
elif obj.type_name == "tree":
683
583
self.cache.idmap._add_git_sha(obj.id, "tree",
684
584
(ie.file_id, self.revid))
685
self._cache_objs.add((obj, path))
585
self.cache.content_cache.add(obj)
687
587
raise AssertionError
689
589
def finish(self):
690
self.cache.content_cache.add_multi(self._cache_objs)
691
590
return self._commit
696
595
def __init__(self, transport=None):
697
596
mapper = versionedfile.ConstantMapper("trees")
698
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,
597
trees_store = knit.make_file_factory(True, mapper)(transport)
598
super(IndexBzrGitCache, self).__init__(
599
IndexGitShaMap(transport.clone('index')),
600
VersionedFilesContentCache(trees_store),
705
601
IndexCacheUpdater)
708
604
class IndexGitCacheFormat(BzrGitCacheFormat):
710
606
def get_format_string(self):
711
return 'bzr-git sha map with git object cache version 1\n'
607
return 'bzr-git sha map version 1\n'
713
609
def initialize(self, transport):
714
610
super(IndexGitCacheFormat, self).initialize(transport)
715
611
transport.mkdir('index')
716
transport.mkdir('objects')
717
from bzrlib.plugins.git.transportgit import TransportObjectStore
718
TransportObjectStore.init(transport.clone('objects'))
720
613
def open(self, transport):
721
614
return IndexBzrGitCache(transport)
835
728
def _add_git_sha(self, hexsha, type, type_data):
836
729
if hexsha is not None:
837
730
self._name.update(hexsha)
839
td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
842
self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
731
self._add_node(("git", hexsha, "X"),
732
" ".join((type, type_data[0], type_data[1])))
844
734
# This object is not represented in Git - perhaps an empty
851
741
def lookup_git_sha(self, sha):
852
742
if len(sha) == 20:
853
743
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]}))
858
return (data[0], tuple(data[1:]))
744
data = self._get_entry(("git", sha, "X")).split(" ", 2)
745
return (data[0], (data[1], data[2]))
860
747
def revids(self):
861
748
"""List the revision ids known."""
883
770
SqliteGitCacheFormat())
884
771
formats.register(IndexGitCacheFormat().get_format_string(),
885
772
IndexGitCacheFormat())
886
# In the future, this will become the default:
887
# formats.register('default', IndexGitCacheFormat())
891
formats.register('default', SqliteGitCacheFormat())
893
formats.register('default', TdbGitCacheFormat())
773
formats.register('default', IndexGitCacheFormat())
897
776
def migrate_ancient_formats(repo_transport):
898
# Prefer migrating git.db over git.tdb, since the latter may not
899
# be openable on some platforms.
900
if repo_transport.has("git.db"):
777
if repo_transport.has("git.tdb"):
778
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
779
repo_transport.rename("git.tdb", "git/idmap.tdb")
780
elif repo_transport.has("git.db"):
901
781
SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
902
782
repo_transport.rename("git.db", "git/idmap.db")
903
elif repo_transport.has("git.tdb"):
904
TdbGitCacheFormat().initialize(repo_transport.clone("git"))
905
repo_transport.rename("git.tdb", "git/idmap.tdb")
908
def remove_readonly_transport_decorator(transport):
909
if transport.is_readonly():
910
return transport._decorated
914
785
def from_repository(repository):
915
"""Open a cache file for a repository.
917
If the repository is remote and there is no transport available from it
918
this will use a local file in the users cache directory
919
(typically ~/.cache/bazaar/git/)
921
:param repository: A repository object
923
786
repo_transport = getattr(repository, "_transport", None)
924
787
if repo_transport is not None:
925
788
# Migrate older cache formats
926
repo_transport = remove_readonly_transport_decorator(repo_transport)
928
790
repo_transport.mkdir("git")
929
791
except bzrlib.errors.FileExists: