/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

Don't import head revision twice when pulling from Git.

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