/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

Defer invshamap calls.

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