/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

Avoid storing entries for trees in the cache, use blobs instead

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
24
 
 
25
 
from bzrlib.plugins.git.mapping import (
26
 
    inventory_to_tree_and_blobs,
27
 
    revision_to_commit,
28
 
    )
29
 
from bzrlib.plugins.git.shamap import GitShaMap
30
 
 
31
19
from dulwich.objects import (
32
20
    Blob,
33
 
    )
34
 
 
35
 
 
36
 
class GitObjectConverter(object):
 
21
    sha_to_hex,
 
22
    )
 
23
from dulwich.object_store import (
 
24
    BaseObjectStore,
 
25
    )
 
26
 
 
27
from bzrlib import (
 
28
    debug,
 
29
    errors,
 
30
    trace,
 
31
    ui,
 
32
    )
 
33
from bzrlib.revision import (
 
34
    NULL_REVISION,
 
35
    )
 
36
 
 
37
from bzrlib.plugins.git.mapping import (
 
38
    default_mapping,
 
39
    directory_to_tree,
 
40
    extract_unusual_modes,
 
41
    mapping_registry,
 
42
    )
 
43
from bzrlib.plugins.git.shamap import (
 
44
    SqliteGitShaMap,
 
45
    TdbGitShaMap,
 
46
    )
 
47
 
 
48
 
 
49
def get_object_store(repo, mapping=None):
 
50
    git = getattr(repo, "_git", None)
 
51
    if git is not None:
 
52
        return git.object_store
 
53
    return BazaarObjectStore(repo, mapping)
 
54
 
 
55
 
 
56
class BazaarObjectStore(BaseObjectStore):
 
57
    """A Git-style object store backed onto a Bazaar repository."""
37
58
 
38
59
    def __init__(self, repository, mapping=None):
39
60
        self.repository = repository
40
61
        if mapping is None:
41
 
            self.mapping = self.repository.get_mapping()
 
62
            self.mapping = default_mapping
42
63
        else:
43
64
            self.mapping = mapping
44
 
        self._idmap = GitShaMap(self.repository._transport)
 
65
        self._idmap = SqliteGitShaMap.from_repository(repository)
 
66
        self.start_write_group = self._idmap.start_write_group
 
67
        self.abort_write_group = self._idmap.abort_write_group
 
68
        self.commit_write_group = self._idmap.commit_write_group
45
69
 
46
 
    def _update_sha_map(self):
47
 
        all_revids = self.repository.all_revision_ids()
 
70
    def _update_sha_map(self, stop_revision=None):
48
71
        graph = self.repository.get_graph()
49
 
        present_revids = set(self._idmap.revids())
50
 
        pb = ui.ui_factory.nested_progress_bar()
 
72
        if stop_revision is None:
 
73
            heads = graph.heads(self.repository.all_revision_ids())
 
74
        else:
 
75
            heads = set([stop_revision])
 
76
        missing_revids = self._idmap.missing_revisions(heads)
 
77
        while heads:
 
78
            parents = graph.get_parent_map(heads)
 
79
            todo = set()
 
80
            for p in parents.values():
 
81
                todo.update([x for x in p if x not in missing_revids])
 
82
            heads = self._idmap.missing_revisions(todo)
 
83
            missing_revids.update(heads)
 
84
        if NULL_REVISION in missing_revids:
 
85
            missing_revids.remove(NULL_REVISION)
 
86
        self.start_write_group()
51
87
        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))
56
 
                self._update_sha_map_revision(revid)
57
 
        finally:
58
 
            pb.finished()
 
88
            pb = ui.ui_factory.nested_progress_bar()
 
89
            try:
 
90
                for i, revid in enumerate(graph.iter_topo_order(missing_revids)):
 
91
                    pb.update("updating git map", i, len(missing_revids))
 
92
                    self._update_sha_map_revision(revid)
 
93
            finally:
 
94
                pb.finished()
 
95
        except:
 
96
            self.abort_write_group()
 
97
            raise
 
98
        else:
 
99
            self.commit_write_group()
 
100
 
 
101
    def __iter__(self):
 
102
        self._update_sha_map()
 
103
        return iter(self._idmap.sha1s())
 
104
 
 
105
    def _revision_to_commit(self, rev, tree_sha):
 
106
        def parent_lookup(revid):
 
107
            try:
 
108
                return self._lookup_revision_sha1(revid)
 
109
            except errors.NoSuchRevision:
 
110
                trace.warning("Ignoring ghost parent %s", revid)
 
111
                return None
 
112
        return self.mapping.export_commit(rev, tree_sha, parent_lookup)
59
113
 
60
114
    def _update_sha_map_revision(self, revid):
61
115
        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))
71
 
        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")
77
 
        blob = Blob()
78
 
        blob._text = text
79
 
        return blob
80
 
 
81
 
    def _get_tree(self, fileid, revid):
82
 
        raise NotImplementedError(self._get_tree)
83
 
 
84
 
    def _get_commit(self, revid, tree_sha):
85
 
        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)
92
 
        except KeyError:
93
 
            # if not, see if there are any unconverted revisions and add them 
 
116
        rev = self.repository.get_revision(revid)
 
117
        unusual_modes = extract_unusual_modes(rev)
 
118
        tree_sha = self._get_ie_sha1(inv.root, inv, unusual_modes)
 
119
        commit_obj = self._revision_to_commit(rev, tree_sha)
 
120
        try:
 
121
            foreign_revid, mapping = mapping_registry.parse_revision_id(revid)
 
122
        except errors.InvalidRevisionId:
 
123
            pass
 
124
        else:
 
125
            if foreign_revid != commit_obj.id:
 
126
                if not "fix-shamap" in debug.debug_flags:
 
127
                    raise AssertionError("recreated git commit had different sha1: expected %s, got %s" % (foreign_revid, commit_obj.id))
 
128
        self._idmap.add_entry(commit_obj.id, "commit", (revid, tree_sha))
 
129
 
 
130
    def _check_expected_sha(self, expected_sha, object):
 
131
        if expected_sha is None:
 
132
            return
 
133
        if len(expected_sha) == 40:
 
134
            if expected_sha != object.sha().hexdigest():
 
135
                raise AssertionError("Invalid sha for %r: %s" % (object, expected_sha))
 
136
        elif len(expected_sha) == 20:
 
137
            if expected_sha != object.sha().digest():
 
138
                raise AssertionError("Invalid sha for %r: %s" % (object, sha_to_hex(expected_sha)))
 
139
        else:
 
140
            raise AssertionError("Unknown length %d for %r" % (len(expected_sha), expected_sha))
 
141
 
 
142
    def _get_ie_object(self, entry, inv, unusual_modes):
 
143
        if entry.kind == "directory":
 
144
            return self._get_tree(entry.file_id, inv.revision_id, inv, unusual_modes)
 
145
        elif entry.kind == "symlink":
 
146
            return self._get_blob_for_symlink(entry.symlink_target)
 
147
        elif entry.kind == "file":
 
148
            return self._get_blob_for_file(entry.file_id, entry.revision)
 
149
        else:
 
150
            raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
151
 
 
152
    def _get_ie_object_or_sha1(self, entry, inv, unusual_modes):
 
153
        if entry.kind == "directory":
 
154
            ret = self._get_ie_object(entry, inv, unusual_modes)
 
155
            if ret is None:
 
156
                # Empty directory
 
157
                hexsha = None
 
158
            else:
 
159
                hexsha = ret.id
 
160
            self._idmap.add_entry(hexsha, "tree",
 
161
                (entry.file_id, inv.revision_id))
 
162
            return hexsha, ret
 
163
        elif entry.kind in ("file", "symlink"):
 
164
            try:
 
165
                return self._idmap.lookup_blob(entry.file_id, entry.revision), None
 
166
            except KeyError:
 
167
                ret = self._get_ie_object(entry, inv, unusual_modes)
 
168
                self._idmap.add_entry(ret.id, "blob", (entry.file_id,
 
169
                    entry.revision))
 
170
                return ret.id, ret
 
171
        else:
 
172
            raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
173
 
 
174
    def _get_ie_sha1(self, entry, inv, unusual_modes):
 
175
        return self._get_ie_object_or_sha1(entry, inv, unusual_modes)[0]
 
176
 
 
177
    def _get_blob_for_symlink(self, symlink_target, expected_sha=None):
 
178
        """Return a Git Blob object for symlink.
 
179
 
 
180
        :param symlink_target: target of symlink.
 
181
        """
 
182
        if type(symlink_target) == unicode:
 
183
            symlink_target = symlink_target.encode('utf-8')
 
184
        blob = Blob()
 
185
        blob._text = symlink_target
 
186
        self._check_expected_sha(expected_sha, blob)
 
187
        return blob
 
188
 
 
189
    def _get_blob_for_file(self, fileid, revision, expected_sha=None):
 
190
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
191
 
 
192
        :param fileid: File id of the text
 
193
        :param revision: Revision of the text
 
194
        """
 
195
        blob = Blob()
 
196
        chunks = self.repository.iter_files_bytes([(fileid, revision, None)]).next()[1]
 
197
        blob._text = "".join(chunks)
 
198
        self._check_expected_sha(expected_sha, blob)
 
199
        return blob
 
200
 
 
201
    def _get_blob(self, fileid, revision, expected_sha=None):
 
202
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
203
 
 
204
        :param fileid: File id of the text
 
205
        :param revision: Revision of the text
 
206
        """
 
207
        inv = self.repository.get_inventory(revision)
 
208
        entry = inv[fileid]
 
209
 
 
210
        if entry.kind == 'file':
 
211
            return self._get_blob_for_file(entry.file_id, entry.revision,
 
212
                                           expected_sha=expected_sha)
 
213
        elif entry.kind == 'symlink':
 
214
            return self._get_blob_for_symlink(entry.symlink_target,
 
215
                                              expected_sha=expected_sha)
 
216
        else:
 
217
            raise AssertionError
 
218
 
 
219
    def _get_tree(self, fileid, revid, inv, unusual_modes, expected_sha=None):
 
220
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
221
 
 
222
        :param fileid: fileid in the tree.
 
223
        :param revision: Revision of the tree.
 
224
        """
 
225
        tree = directory_to_tree(inv[fileid],
 
226
            lambda ie: self._get_ie_sha1(ie, inv, unusual_modes),
 
227
            unusual_modes)
 
228
        self._check_expected_sha(expected_sha, tree)
 
229
        return tree
 
230
 
 
231
    def _get_commit(self, rev, tree_sha, expected_sha=None):
 
232
        commit = self._revision_to_commit(rev, tree_sha)
 
233
        self._check_expected_sha(expected_sha, commit)
 
234
        return commit
 
235
 
 
236
    def get_parents(self, sha):
 
237
        """Retrieve the parents of a Git commit by SHA1.
 
238
 
 
239
        :param sha: SHA1 of the commit
 
240
        :raises: KeyError, NotCommitError
 
241
        """
 
242
        return self[sha].parents
 
243
 
 
244
    def _lookup_revision_sha1(self, revid):
 
245
        """Return the SHA1 matching a Bazaar revision."""
 
246
        if revid == NULL_REVISION:
 
247
            return "0" * 40
 
248
        try:
 
249
            return self._idmap.lookup_commit(revid)
 
250
        except KeyError:
 
251
            try:
 
252
                return mapping_registry.parse_revision_id(revid)[0]
 
253
            except errors.InvalidRevisionId:
 
254
                self._update_sha_map(revid)
 
255
                return self._idmap.lookup_commit(revid)
 
256
 
 
257
    def get_raw(self, sha):
 
258
        """Get the raw representation of a Git object by SHA1.
 
259
 
 
260
        :param sha: SHA1 of the git object
 
261
        """
 
262
        obj = self[sha]
 
263
        return (obj.type, obj.as_raw_string())
 
264
 
 
265
    def __contains__(self, sha):
 
266
        # See if sha is in map
 
267
        try:
 
268
            (type, type_data) = self._lookup_git_sha(sha)
 
269
            if type == "commit":
 
270
                return self.repository.has_revision(type_data[0])
 
271
            elif type == "blob":
 
272
                return self.repository.texts.has_version(type_data)
 
273
            elif type == "tree":
 
274
                return self.repository.has_revision(type_data[1])
 
275
            else:
 
276
                raise AssertionError("Unknown object type '%s'" % type)
 
277
        except KeyError:
 
278
            return False
 
279
        else:
 
280
            return True
 
281
 
 
282
    def _lookup_git_sha(self, sha):
 
283
        # See if sha is in map
 
284
        try:
 
285
            return self._idmap.lookup_git_sha(sha)
 
286
        except KeyError:
 
287
            # if not, see if there are any unconverted revisions and add them
94
288
            # to the map, search for sha in map again
95
289
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
290
            return self._idmap.lookup_git_sha(sha)
 
291
 
 
292
    def __getitem__(self, sha):
 
293
        (type, type_data) = self._lookup_git_sha(sha)
97
294
        # convert object to git object
98
295
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
296
            try:
 
297
                rev = self.repository.get_revision(type_data[0])
 
298
            except errors.NoSuchRevision:
 
299
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
300
                raise KeyError(sha)
 
301
            return self._get_commit(rev, type_data[1], expected_sha=sha)
100
302
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
303
            return self._get_blob(type_data[0], type_data[1], expected_sha=sha)
102
304
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
305
            try:
 
306
                inv = self.repository.get_inventory(type_data[1])
 
307
                rev = self.repository.get_revision(type_data[1])
 
308
            except errors.NoSuchRevision:
 
309
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
310
                raise KeyError(sha)
 
311
            unusual_modes = extract_unusual_modes(rev)
 
312
            try:
 
313
                return self._get_tree(type_data[0], type_data[1], inv,
 
314
                    unusual_modes, expected_sha=sha)
 
315
            except errors.NoSuchRevision:
 
316
                raise KeyError(sha)
104
317
        else:
105
318
            raise AssertionError("Unknown object type '%s'" % type)