/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 loading bzr-git/dulwich when not necessary.

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