/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 object_store.py

More tests for sha maps, fix cache misses in tdb.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2009 Canonical Ltd
 
1
# Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Map from Git sha's to Bazaar objects."""
18
18
 
19
 
import bzrlib
20
 
 
21
 
from bzrlib import ui
22
 
 
23
 
from bzrlib.errors import NoSuchRevision
 
19
from dulwich.objects import (
 
20
    Blob,
 
21
    Tree,
 
22
    )
 
23
from dulwich.object_store import (
 
24
    BaseObjectStore,
 
25
    ObjectStoreIterator,
 
26
    )
 
27
import stat
 
28
 
 
29
from bzrlib import (
 
30
    debug,
 
31
    errors,
 
32
    ui,
 
33
    )
 
34
 
 
35
from bzrlib.plugins.git.errors import (
 
36
    GhostRevision,
 
37
    )
24
38
 
25
39
from bzrlib.plugins.git.mapping import (
26
 
    inventory_to_tree_and_blobs,
 
40
    default_mapping,
 
41
    directory_to_tree,
 
42
    mapping_registry,
27
43
    revision_to_commit,
28
44
    )
29
 
from bzrlib.plugins.git.shamap import GitShaMap
30
 
 
31
 
from dulwich.objects import (
32
 
    Blob,
 
45
from bzrlib.plugins.git.shamap import (
 
46
    SqliteGitShaMap,
 
47
    TdbGitShaMap,
33
48
    )
34
49
 
35
50
 
36
 
class GitObjectConverter(object):
 
51
def get_object_store(repo, mapping=None):
 
52
    git = getattr(repo, "_git", None)
 
53
    if git is not None:
 
54
        return git.object_store
 
55
    return BazaarObjectStore(repo, mapping)
 
56
 
 
57
 
 
58
class BazaarObjectStore(BaseObjectStore):
 
59
    """A Git-style object store backed onto a Bazaar repository."""
37
60
 
38
61
    def __init__(self, repository, mapping=None):
39
62
        self.repository = repository
40
63
        if mapping is None:
41
 
            self.mapping = self.repository.get_mapping()
 
64
            self.mapping = default_mapping
42
65
        else:
43
66
            self.mapping = mapping
44
 
        self._idmap = GitShaMap(self.repository._transport)
 
67
        try:
 
68
            self._idmap = TdbGitShaMap.from_repository(repository)
 
69
        except ImportError:
 
70
            self._idmap = SqliteGitShaMap.from_repository(repository)
45
71
 
46
 
    def _update_sha_map(self):
47
 
        all_revids = self.repository.all_revision_ids()
 
72
    def _update_sha_map(self, stop_revision=None):
 
73
        if stop_revision is None:
 
74
            all_revids = self.repository.all_revision_ids()
 
75
        else:
 
76
            all_revids = self.repository.get_ancestry(stop_revision)
 
77
            first = all_revids.pop(0) # Pop leading None
 
78
            assert first is None
48
79
        graph = self.repository.get_graph()
49
80
        present_revids = set(self._idmap.revids())
 
81
        missing_revids = [revid for revid in graph.iter_topo_order(all_revids) if revid not in present_revids]
50
82
        pb = ui.ui_factory.nested_progress_bar()
51
83
        try:
52
 
            for i, revid in enumerate(graph.iter_topo_order(all_revids)):
53
 
                if revid in present_revids:
54
 
                    continue
55
 
                pb.update("updating git map", i, len(all_revids))
 
84
            for i, revid in enumerate(missing_revids):
 
85
                pb.update("updating git map", i, len(missing_revids))
56
86
                self._update_sha_map_revision(revid)
57
87
        finally:
 
88
            self._idmap.commit()
58
89
            pb.finished()
59
90
 
 
91
    def __iter__(self):
 
92
        self._update_sha_map()
 
93
        return iter(self._idmap.sha1s())
 
94
 
60
95
    def _update_sha_map_revision(self, revid):
61
96
        inv = self.repository.get_inventory(revid)
62
 
        objects = inventory_to_tree_and_blobs(self.repository, self.mapping, revid)
63
 
        for sha, o, path in objects:
64
 
            if path == "":
65
 
                tree_sha = sha
66
 
            ie = inv[inv.path2id(path)]
67
 
            if ie.kind in ("file", "symlink"):
68
 
                self._idmap.add_entry(sha, "blob", (ie.file_id, ie.revision))
69
 
            else:
70
 
                self._idmap.add_entry(sha, "tree", (ie.file_id, ie.revision))
 
97
        tree_sha = self._get_ie_sha1(inv.root, inv)
71
98
        rev = self.repository.get_revision(revid)
72
 
        commit_obj = revision_to_commit(rev, tree_sha, self._idmap._parent_lookup)
73
 
        self._idmap.add_entry(commit_obj.sha().hexdigest(), "commit", (revid, tree_sha))
74
 
 
75
 
    def _get_blob(self, fileid, revision):
76
 
        text = self.repository.texts.get_record_stream([(fileid, revision)], "unordered", True).next().get_bytes_as("fulltext")
 
99
        commit_obj = revision_to_commit(rev, tree_sha,
 
100
            self._idmap._parent_lookup)
 
101
        try:
 
102
            foreign_revid, mapping = mapping_registry.parse_revision_id(revid)
 
103
        except errors.InvalidRevisionId:
 
104
            pass
 
105
        else:
 
106
            if foreign_revid != commit_obj.id:
 
107
                if not "fix-shamap" in debug.debug_flags:
 
108
                    raise AssertionError("recreated git commit had different sha1: expected %s, got %s" % (foreign_revid, commit_obj.id))
 
109
        self._idmap.add_entry(commit_obj.id, "commit", (revid, tree_sha))
 
110
 
 
111
    def _check_expected_sha(self, expected_sha, object):
 
112
        if expected_sha is None:
 
113
            return
 
114
        if expected_sha != object.id:
 
115
            raise AssertionError("Invalid sha for %r: %s" % (object, expected_sha))
 
116
 
 
117
    def _get_ie_object(self, entry, inv):  
 
118
        if entry.kind == "directory":
 
119
            return self._get_tree(entry.file_id, inv.revision_id, inv=inv)
 
120
        else:
 
121
            return self._get_blob(entry.file_id, entry.revision)
 
122
 
 
123
    def _get_ie_object_or_sha1(self, entry, inv):
 
124
        if entry.kind == "directory":
 
125
            try:
 
126
                return self._idmap.lookup_tree(entry.file_id, inv.revision_id), None
 
127
            except KeyError:
 
128
                ret = self._get_ie_object(entry, inv)
 
129
                self._idmap.add_entry(ret.id, "tree", (entry.file_id, inv.revision_id))
 
130
                return ret.id, ret
 
131
        else:
 
132
            try:
 
133
                return self._idmap.lookup_blob(entry.file_id, entry.revision), None
 
134
            except KeyError:
 
135
                ret = self._get_ie_object(entry, inv)
 
136
                self._idmap.add_entry(ret.id, "blob", (entry.file_id, entry.revision))
 
137
                return ret.id, ret
 
138
 
 
139
    def _get_ie_sha1(self, entry, inv):
 
140
        return self._get_ie_object_or_sha1(entry, inv)[0]
 
141
 
 
142
    def _get_blob(self, fileid, revision, expected_sha=None):
 
143
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
144
        
 
145
        :param fileid: File id of the text
 
146
        :param revision: Revision of the text
 
147
        """
 
148
        text = self.repository.texts.get_record_stream([(fileid, revision)],
 
149
            "unordered", True).next().get_bytes_as("fulltext")
77
150
        blob = Blob()
78
151
        blob._text = text
 
152
        self._check_expected_sha(expected_sha, blob)
79
153
        return blob
80
154
 
81
 
    def _get_tree(self, fileid, revid):
82
 
        raise NotImplementedError(self._get_tree)
83
 
 
84
 
    def _get_commit(self, revid, tree_sha):
 
155
    def _get_tree(self, fileid, revid, inv=None, expected_sha=None):
 
156
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
157
 
 
158
        :param fileid: fileid in the tree.
 
159
        :param revision: Revision of the tree.
 
160
        """
 
161
        if inv is None:
 
162
            inv = self.repository.get_inventory(revid)
 
163
        tree = directory_to_tree(inv[fileid], lambda ie: self._get_ie_sha1(ie, inv))
 
164
        self._check_expected_sha(expected_sha, tree)
 
165
        return tree
 
166
 
 
167
    def _get_commit(self, revid, tree_sha, expected_sha=None):
85
168
        rev = self.repository.get_revision(revid)
86
 
        return revision_to_commit(rev, tree_sha, self._idmap._parent_lookup)
87
 
 
88
 
    def __getitem__(self, sha):
89
 
        # See if sha is in map
90
 
        try:
91
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
169
        try:
 
170
            commit = revision_to_commit(rev, tree_sha, self._lookup_revision_sha1)
 
171
        except errors.NoSuchRevision, e:
 
172
            raise GhostRevision(e.branch, e.revision)
 
173
        self._check_expected_sha(expected_sha, commit)
 
174
        return commit
 
175
 
 
176
    def get_parents(self, sha):
 
177
        """Retrieve the parents of a Git commit by SHA1.
 
178
 
 
179
        :param sha: SHA1 of the commit
 
180
        :raises: KeyError, NotCommitError
 
181
        """
 
182
        return self[sha].parents
 
183
 
 
184
    def _lookup_revision_sha1(self, revid):
 
185
        """Return the SHA1 matching a Bazaar revision."""
 
186
        try:
 
187
            return self._idmap._parent_lookup(revid)
 
188
        except KeyError:
 
189
            inv = self.repository.get_inventory(revid)
 
190
            tree_sha = self._get_ie_sha1(inv.root, inv)
 
191
            ret = self._get_commit(revid, tree_sha).id
 
192
            self._idmap.add_entry(ret, "commit", (revid, tree_sha))
 
193
            return ret
 
194
 
 
195
    def get_raw(self, sha):
 
196
        """Get the raw representation of a Git object by SHA1.
 
197
 
 
198
        :param sha: SHA1 of the git object
 
199
        """
 
200
        return self[sha].as_raw_string()
 
201
 
 
202
    def __contains__(self, sha):
 
203
        # See if sha is in map
 
204
        try:
 
205
            self._lookup_git_sha(sha)
 
206
        except KeyError:
 
207
            return False
 
208
        else:
 
209
            return True
 
210
 
 
211
    def _lookup_git_sha(self, sha):
 
212
        # See if sha is in map
 
213
        try:
 
214
            return self._idmap.lookup_git_sha(sha)
92
215
        except KeyError:
93
216
            # if not, see if there are any unconverted revisions and add them 
94
217
            # to the map, search for sha in map again
95
218
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
219
            return self._idmap.lookup_git_sha(sha)
 
220
 
 
221
    def __getitem__(self, sha):
 
222
        (type, type_data) = self._lookup_git_sha(sha)
97
223
        # convert object to git object
98
224
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
225
            return self._get_commit(type_data[0], type_data[1], 
 
226
                                    expected_sha=sha)
100
227
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
228
            return self._get_blob(type_data[0], type_data[1], expected_sha=sha)
102
229
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
230
            return self._get_tree(type_data[0], type_data[1], expected_sha=sha)
104
231
        else:
105
232
            raise AssertionError("Unknown object type '%s'" % type)