/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

Prepare for using add_entries everywhere.

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
        tree_sha = self._get_ie_sha1(inv.root, inv, unusual_modes)
 
122
        commit_obj = self._revision_to_commit(rev, tree_sha)
 
123
        try:
 
124
            foreign_revid, mapping = mapping_registry.parse_revision_id(revid)
 
125
        except errors.InvalidRevisionId:
 
126
            pass
 
127
        else:
 
128
            if foreign_revid != commit_obj.id:
 
129
                if not "fix-shamap" in debug.debug_flags:
 
130
                    raise AssertionError("recreated git commit had different sha1: expected %s, got %s" % (foreign_revid, commit_obj.id))
 
131
        self._idmap.add_entry(commit_obj.id, "commit", (revid, tree_sha))
 
132
 
 
133
    def _check_expected_sha(self, expected_sha, object):
 
134
        if expected_sha is None:
 
135
            return
 
136
        if len(expected_sha) == 40:
 
137
            if expected_sha != object.sha().hexdigest():
 
138
                raise AssertionError("Invalid sha for %r: %s" % (object, expected_sha))
 
139
        elif len(expected_sha) == 20:
 
140
            if expected_sha != object.sha().digest():
 
141
                raise AssertionError("Invalid sha for %r: %s" % (object, sha_to_hex(expected_sha)))
 
142
        else:
 
143
            raise AssertionError("Unknown length %d for %r" % (len(expected_sha), expected_sha))
 
144
 
 
145
    def _get_ie_object(self, entry, inv, unusual_modes):
 
146
        if entry.kind == "directory":
 
147
            return self._get_tree(entry.file_id, inv.revision_id, inv,
 
148
                unusual_modes)
 
149
        elif entry.kind == "symlink":
 
150
            return self._get_blob_for_symlink(entry.symlink_target)
 
151
        elif entry.kind == "file":
 
152
            return self._get_blob_for_file(entry.file_id, entry.revision)
 
153
        else:
 
154
            raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
155
 
 
156
    def _get_ie_object_or_sha1(self, entry, inv, unusual_modes):
 
157
        # FIXME: Pass in?
 
158
        invshamap = self._idmap.get_inventory_sha_map(inv.revision_id)
 
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, unusual_modes):
 
184
        return self._get_ie_object_or_sha1(entry, inv, 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
        tree = directory_to_tree(inv[fileid],
 
235
            lambda ie: self._get_ie_sha1(ie, inv, unusual_modes),
 
236
            unusual_modes)
 
237
        self._check_expected_sha(expected_sha, tree)
 
238
        return tree
 
239
 
 
240
    def _get_commit(self, rev, tree_sha, expected_sha=None):
 
241
        commit = self._revision_to_commit(rev, tree_sha)
 
242
        self._check_expected_sha(expected_sha, commit)
 
243
        return commit
 
244
 
 
245
    def get_parents(self, sha):
 
246
        """Retrieve the parents of a Git commit by SHA1.
 
247
 
 
248
        :param sha: SHA1 of the commit
 
249
        :raises: KeyError, NotCommitError
 
250
        """
 
251
        return self[sha].parents
 
252
 
 
253
    def _lookup_revision_sha1(self, revid):
 
254
        """Return the SHA1 matching a Bazaar revision."""
 
255
        if revid == NULL_REVISION:
 
256
            return "0" * 40
 
257
        try:
 
258
            return self._idmap.lookup_commit(revid)
 
259
        except KeyError:
 
260
            try:
 
261
                return mapping_registry.parse_revision_id(revid)[0]
 
262
            except errors.InvalidRevisionId:
 
263
                self._update_sha_map(revid)
 
264
                return self._idmap.lookup_commit(revid)
 
265
 
 
266
    def get_raw(self, sha):
 
267
        """Get the raw representation of a Git object by SHA1.
 
268
 
 
269
        :param sha: SHA1 of the git object
 
270
        """
 
271
        obj = self[sha]
 
272
        return (obj.type, obj.as_raw_string())
 
273
 
 
274
    def __contains__(self, sha):
 
275
        # See if sha is in map
 
276
        try:
 
277
            (type, type_data) = self._lookup_git_sha(sha)
 
278
            if type == "commit":
 
279
                return self.repository.has_revision(type_data[0])
 
280
            elif type == "blob":
 
281
                return self.repository.texts.has_version(type_data)
 
282
            elif type == "tree":
 
283
                return self.repository.has_revision(type_data[1])
69
284
            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):
 
285
                raise AssertionError("Unknown object type '%s'" % type)
 
286
        except KeyError:
 
287
            return False
 
288
 
 
289
    def _lookup_git_sha(self, sha):
89
290
        # See if sha is in map
90
291
        try:
91
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
292
            return self._idmap.lookup_git_sha(sha)
92
293
        except KeyError:
93
 
            # if not, see if there are any unconverted revisions and add them 
 
294
            # if not, see if there are any unconverted revisions and add them
94
295
            # to the map, search for sha in map again
95
296
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
297
            return self._idmap.lookup_git_sha(sha)
 
298
 
 
299
    def __getitem__(self, sha):
 
300
        (type, type_data) = self._lookup_git_sha(sha)
97
301
        # convert object to git object
98
302
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
303
            try:
 
304
                rev = self.repository.get_revision(type_data[0])
 
305
            except errors.NoSuchRevision:
 
306
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
307
                raise KeyError(sha)
 
308
            return self._get_commit(rev, type_data[1], expected_sha=sha)
100
309
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
310
            return self._get_blob(type_data[0], type_data[1], expected_sha=sha)
102
311
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
312
            try:
 
313
                inv = self.repository.get_inventory(type_data[1])
 
314
                rev = self.repository.get_revision(type_data[1])
 
315
            except errors.NoSuchRevision:
 
316
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
317
                raise KeyError(sha)
 
318
            unusual_modes = extract_unusual_modes(rev)
 
319
            try:
 
320
                return self._get_tree(type_data[0], type_data[1], inv,
 
321
                    unusual_modes, expected_sha=sha)
 
322
            except errors.NoSuchRevision:
 
323
                raise KeyError(sha)
104
324
        else:
105
325
            raise AssertionError("Unknown object type '%s'" % type)