/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to shamap.py

  • Committer: Jelmer Vernooij
  • Date: 2010-04-02 19:15:13 UTC
  • mto: (0.200.951 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20100402191513-08c0c8y7oe9dr3z6
Also cache full contents of symlinks.

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
from bzrlib import (
32
32
    btree_index as _mod_btree_index,
33
33
    index as _mod_index,
 
34
    knit,
34
35
    osutils,
35
36
    registry,
36
37
    trace,
55
56
 
56
57
 
57
58
def get_remote_cache_transport():
58
 
    """Retrieve the transport to use when accessing (unwritable) remote 
59
 
    repositories.
60
 
    """
61
59
    return get_transport(get_cache_dir())
62
60
 
63
61
 
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
105
 
            blob: fileid, revid
106
 
            tree: fileid, revid
 
102
            revision: revid, tree sha
107
103
        """
108
104
        raise NotImplementedError(self.lookup_git_sha)
109
105
 
148
144
class ContentCache(object):
149
145
    """Object that can cache Git objects."""
150
146
 
151
 
    def add(self, object):
152
 
        """Add an object."""
153
 
        raise NotImplementedError(self.add)
154
 
 
155
 
    def add_multi(self, objects):
156
 
        """Add multiple objects."""
157
 
        for obj in objects:
158
 
            self.add(obj)
159
 
 
160
147
    def __getitem__(self, sha):
161
148
        """Retrieve an item, by SHA."""
162
149
        raise NotImplementedError(self.__getitem__)
163
150
 
164
151
 
165
152
class BzrGitCacheFormat(object):
166
 
    """Bazaar-Git Cache Format."""
167
153
 
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)
171
156
 
172
157
    def open(self, transport):
173
 
        """Open this format on a transport."""
174
158
        raise NotImplementedError(self.open)
175
159
 
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())
179
162
 
180
163
    @classmethod
181
 
    def from_transport(self, transport):
182
 
        """Open a cache file present on a transport, or initialize one.
183
 
 
184
 
        :param transport: Transport to use
185
 
        :return: A BzrGitCache instance
186
 
        """
 
164
    def from_repository(self, repository):
 
165
        repo_transport = getattr(repository, "_transport", None)
 
166
        if repo_transport is not None:
 
167
            try:
 
168
                repo_transport.mkdir('git')
 
169
            except bzrlib.errors.FileExists:
 
170
                pass
 
171
            transport = repo_transport.clone('git')
 
172
        else:
 
173
            transport = get_remote_cache_transport()
187
174
        try:
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)
194
181
 
195
 
    @classmethod
196
 
    def from_repository(cls, repository):
197
 
        """Open a cache file for a repository.
198
 
 
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.
202
 
 
203
 
        :param repository: Repository to open the cache for
204
 
        :return: A `BzrGitCache`
205
 
        """
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)
211
 
            try:
212
 
                repo_transport.mkdir('git')
213
 
            except bzrlib.errors.FileExists:
214
 
                pass
215
 
            transport = repo_transport.clone('git')
216
 
        else:
217
 
            transport = get_remote_cache_transport()
218
 
        return cls.from_transport(transport)
219
 
 
220
182
 
221
183
class CacheUpdater(object):
222
 
    """Base class for objects that can update a bzr-git cache."""
223
 
 
224
 
    def add_object(self, obj, ie, path):
225
 
        """Add an object.
226
 
 
227
 
        :param obj: Object type ("commit", "blob" or "tree")
228
 
        :param ie: Inventory entry (for blob/tree) or testament_sha in case
229
 
            of commit
230
 
        :param path: Path of the object (optional)
231
 
        """
 
184
 
 
185
    def add_object(self, obj, ie):
232
186
        raise NotImplementedError(self.add_object)
233
187
 
234
188
    def finish(self):
244
198
        self._cache_updater_klass = cache_updater_klass
245
199
 
246
200
    def get_updater(self, rev):
247
 
        """Update an object that implements the CacheUpdater interface for 
248
 
        updating this cache.
249
 
        """
250
201
        return self._cache_updater_klass(self, rev)
251
202
 
252
203
 
254
205
 
255
206
 
256
207
class DictCacheUpdater(CacheUpdater):
257
 
    """Cache updater for dict-based caches."""
258
208
 
259
209
    def __init__(self, cache, rev):
260
210
        self.cache = cache
263
213
        self._commit = None
264
214
        self._entries = []
265
215
 
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
 
219
            assert ie is None
 
220
            type_data = (self.revid, self._commit.tree)
272
221
        elif obj.type_name in ("blob", "tree"):
273
 
            if ie is not None:
274
 
                if obj.type_name == "blob":
275
 
                    revision = ie.revision
276
 
                else:
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
 
224
            else:
 
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
280
228
        else:
281
229
            raise AssertionError
282
230
        self.cache.idmap._by_sha[obj.id] = (obj.type_name, type_data)
288
236
 
289
237
 
290
238
class DictGitShaMap(GitShaMap):
291
 
    """Git SHA map that uses a dictionary."""
292
239
 
293
240
    def __init__(self):
294
241
        self._by_sha = {}
295
242
        self._by_fileid = {}
296
 
        self._by_revid = {}
297
243
 
298
244
    def lookup_blob_id(self, fileid, revision):
299
245
        return self._by_fileid[revision][fileid]
302
248
        return self._by_sha[sha]
303
249
 
304
250
    def lookup_tree_id(self, fileid, revision):
305
 
        return self._by_fileid[revision][fileid]
306
 
 
307
 
    def lookup_commit(self, revid):
308
 
        return self._by_revid[revid]
 
251
        return self._base._by_fileid[revision][fileid]
309
252
 
310
253
    def revids(self):
311
254
        for key, (type, type_data) in self._by_sha.iteritems():
326
269
        self._trees = []
327
270
        self._blobs = []
328
271
 
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
 
275
            assert ie is None
334
276
        elif obj.type_name == "tree":
335
 
            if ie is not None:
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":
338
 
            if ie is not None:
339
 
                self._blobs.append((obj.id, ie.file_id, ie.revision))
 
279
            self._blobs.append((obj.id, ie.file_id, ie.revision))
340
280
        else:
341
281
            raise AssertionError
342
282
 
350
290
            "replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
351
291
            self._blobs)
352
292
        self.db.execute(
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
356
296
 
357
297
 
372
312
 
373
313
 
374
314
class SqliteGitShaMap(GitShaMap):
375
 
    """Bazaar GIT Sha map that uses a sqlite database for storage."""
376
315
 
377
316
    def __init__(self, path=None):
378
317
        self.path = path
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);
408
347
""")
409
 
        try:
410
 
            self.db.executescript(
411
 
                "ALTER TABLE commits ADD testament3_sha1 TEXT;")
412
 
        except sqlite3.OperationalError:
413
 
            pass # Column already exists.
414
348
 
415
349
    def __repr__(self):
416
350
        return "%s(%r)" % (self.__class__.__name__, self.path)
417
 
 
 
351
    
418
352
    def lookup_commit(self, revid):
419
 
        cursor = self.db.execute("select sha1 from commits where revid = ?", 
420
 
            (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:
423
355
            return row[0]
424
356
        raise KeyError
433
365
        raise KeyError(fileid)
434
366
 
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:
438
370
            return row[0]
439
371
        raise KeyError(fileid)
443
375
 
444
376
        :param sha: Git object sha
445
377
        :return: (type, type_data) with type_data:
446
 
            commit: revid, tree sha, verifiers
447
 
            tree: fileid, revid
448
 
            blob: fileid, revid
 
378
            revision: revid, tree sha
449
379
        """
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)
470
400
 
471
401
 
472
402
class TdbCacheUpdater(CacheUpdater):
473
 
    """Cache updater for tdb-based caches."""
474
403
 
475
404
    def __init__(self, cache, rev):
476
405
        self.cache = cache
480
409
        self._commit = None
481
410
        self._entries = []
482
411
 
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
 
418
            assert ie is None
490
419
        elif obj.type_name == "blob":
491
 
            if ie is None:
492
 
                return
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":
496
 
            if ie is None:
497
 
                return
498
423
            type_data = (ie.file_id, self.revid)
499
424
        else:
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]))
502
428
 
503
429
    def finish(self):
504
430
        if self._commit is None:
509
435
TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), None, TdbCacheUpdater)
510
436
 
511
437
class TdbGitCacheFormat(BzrGitCacheFormat):
512
 
    """Cache format for tdb-based caches."""
513
438
 
514
439
    def get_format_string(self):
515
440
        return 'bzr-git sha map version 3 using tdb\n'
580
505
 
581
506
    def lookup_blob_id(self, fileid, revision):
582
507
        return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
583
 
 
 
508
                
584
509
    def lookup_git_sha(self, sha):
585
510
        """Lookup a Git sha in the database.
586
511
 
587
512
        :param sha: Git object sha
588
513
        :return: (type, type_data) with type_data:
589
 
            commit: revid, tree sha
590
 
            blob: fileid, revid
591
 
            tree: fileid, revid
 
514
            revision: revid, tree sha
592
515
        """
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":
597
 
            if len(data) == 3:
598
 
                return (data[0], (data[1], data[2], {}))
599
 
            else:
600
 
                return (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
601
 
        else:
602
 
            return (data[0], tuple(data[1:]))
 
519
        return (data[0], (data[1], data[2]))
603
520
 
604
521
    def missing_revisions(self, revids):
605
522
        ret = set()
639
556
        return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
640
557
 
641
558
 
642
 
class GitObjectStoreContentCache(ContentCache):
643
 
 
644
 
    def __init__(self, store):
645
 
        self.store = store
646
 
 
647
 
    def add_multi(self, objs):
648
 
        self.store.add_objects(objs)
649
 
 
650
 
    def add(self, obj, path):
651
 
        self.store.add_object(obj)
652
 
 
653
 
    def __getitem__(self, sha):
654
 
        return self.store[sha]
655
 
 
656
 
 
657
559
class IndexCacheUpdater(CacheUpdater):
658
560
 
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()
666
567
 
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
 
571
            assert ie is None
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)
686
586
        else:
687
587
            raise AssertionError
688
588
 
689
589
    def finish(self):
690
 
        self.cache.content_cache.add_multi(self._cache_objs)
691
590
        return self._commit
692
591
 
693
592
 
695
594
 
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)
706
602
 
707
603
 
708
604
class IndexGitCacheFormat(BzrGitCacheFormat):
709
605
 
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'
712
608
 
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'))
719
612
 
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)
838
 
            if type == "commit":
839
 
                td = (type_data[0], type_data[1], type_data[2]["testament3-sha1"])
840
 
            else:
841
 
                td = type_data
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])))
843
733
        else:
844
734
            # This object is not represented in Git - perhaps an empty
845
735
            # directory?
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]}))
857
 
        else:
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]))
859
746
 
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())
888
 
try:
889
 
    import tdb
890
 
except ImportError:
891
 
    formats.register('default', SqliteGitCacheFormat())
892
 
else:
893
 
    formats.register('default', TdbGitCacheFormat())
894
 
 
 
773
formats.register('default', IndexGitCacheFormat())
895
774
 
896
775
 
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")
906
 
 
907
 
 
908
 
def remove_readonly_transport_decorator(transport):
909
 
    if transport.is_readonly():
910
 
        return transport._decorated
911
 
    return transport
912
783
 
913
784
 
914
785
def from_repository(repository):
915
 
    """Open a cache file for a repository.
916
 
 
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/)
920
 
 
921
 
    :param repository: A repository object
922
 
    """
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)
927
789
        try:
928
790
            repo_transport.mkdir("git")
929
791
        except bzrlib.errors.FileExists: