/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

Add test for ShaMap.missing_revisions().

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))
 
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
            try:
 
155
                return self._idmap.lookup_tree(entry.file_id, inv.revision_id), None
 
156
            except KeyError:
 
157
                ret = self._get_ie_object(entry, inv, unusual_modes)
 
158
                if ret is None:
 
159
                    # Empty directory
 
160
                    hexsha = None
 
161
                else:
 
162
                    hexsha = ret.id
 
163
                self._idmap.add_entry(hexsha, "tree",
 
164
                    (entry.file_id, inv.revision_id))
 
165
                return hexsha, ret
 
166
        elif entry.kind in ("file", "symlink"):
 
167
            try:
 
168
                return self._idmap.lookup_blob(entry.file_id, entry.revision), None
 
169
            except KeyError:
 
170
                ret = self._get_ie_object(entry, inv, unusual_modes)
 
171
                self._idmap.add_entry(ret.id, "blob", (entry.file_id, entry.revision))
 
172
                return ret.id, ret
 
173
        else:
 
174
            raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
175
 
 
176
    def _get_ie_sha1(self, entry, inv, unusual_modes):
 
177
        return self._get_ie_object_or_sha1(entry, inv, unusual_modes)[0]
 
178
 
 
179
    def _get_blob_for_symlink(self, symlink_target, expected_sha=None):
 
180
        """Return a Git Blob object for symlink.
 
181
 
 
182
        :param symlink_target: target of symlink.
 
183
        """
 
184
        if type(symlink_target) == unicode:
 
185
            symlink_target = symlink_target.encode('utf-8')
 
186
        blob = Blob()
 
187
        blob._text = symlink_target
 
188
        self._check_expected_sha(expected_sha, blob)
 
189
        return blob
 
190
 
 
191
    def _get_blob_for_file(self, fileid, revision, expected_sha=None):
 
192
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
193
 
 
194
        :param fileid: File id of the text
 
195
        :param revision: Revision of the text
 
196
        """
 
197
        blob = Blob()
 
198
        chunks = self.repository.iter_files_bytes([(fileid, revision, None)]).next()[1]
 
199
        blob._text = "".join(chunks)
 
200
        self._check_expected_sha(expected_sha, blob)
 
201
        return blob
 
202
 
 
203
    def _get_blob(self, fileid, revision, expected_sha=None):
 
204
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
205
 
 
206
        :param fileid: File id of the text
 
207
        :param revision: Revision of the text
 
208
        """
 
209
        inv = self.repository.get_inventory(revision)
 
210
        entry = inv[fileid]
 
211
 
 
212
        if entry.kind == 'file':
 
213
            return self._get_blob_for_file(entry.file_id, entry.revision,
 
214
                                           expected_sha=expected_sha)
 
215
        elif entry.kind == 'symlink':
 
216
            return self._get_blob_for_symlink(entry.symlink_target,
 
217
                                              expected_sha=expected_sha)
 
218
        else:
 
219
            raise AssertionError
 
220
 
 
221
    def _get_tree(self, fileid, revid, inv, unusual_modes, expected_sha=None):
 
222
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
223
 
 
224
        :param fileid: fileid in the tree.
 
225
        :param revision: Revision of the tree.
 
226
        """
 
227
        tree = directory_to_tree(inv[fileid],
 
228
            lambda ie: self._get_ie_sha1(ie, inv, unusual_modes),
 
229
            unusual_modes)
 
230
        self._check_expected_sha(expected_sha, tree)
 
231
        return tree
 
232
 
 
233
    def _get_commit(self, rev, tree_sha, expected_sha=None):
 
234
        commit = self._revision_to_commit(rev, tree_sha)
 
235
        self._check_expected_sha(expected_sha, commit)
 
236
        return commit
 
237
 
 
238
    def get_parents(self, sha):
 
239
        """Retrieve the parents of a Git commit by SHA1.
 
240
 
 
241
        :param sha: SHA1 of the commit
 
242
        :raises: KeyError, NotCommitError
 
243
        """
 
244
        return self[sha].parents
 
245
 
 
246
    def _lookup_revision_sha1(self, revid):
 
247
        """Return the SHA1 matching a Bazaar revision."""
 
248
        if revid == NULL_REVISION:
 
249
            return "0" * 40
 
250
        try:
 
251
            return self._idmap.lookup_commit(revid)
 
252
        except KeyError:
 
253
            try:
 
254
                return mapping_registry.parse_revision_id(revid)[0]
 
255
            except errors.InvalidRevisionId:
 
256
                self._update_sha_map(revid)
 
257
                return self._idmap.lookup_commit(revid)
 
258
 
 
259
    def get_raw(self, sha):
 
260
        """Get the raw representation of a Git object by SHA1.
 
261
 
 
262
        :param sha: SHA1 of the git object
 
263
        """
 
264
        obj = self[sha]
 
265
        return (obj.type, obj.as_raw_string())
 
266
 
 
267
    def __contains__(self, sha):
 
268
        # See if sha is in map
 
269
        try:
 
270
            (type, type_data) = self._lookup_git_sha(sha)
 
271
            if type == "commit":
 
272
                return self.repository.has_revision(type_data[0])
 
273
            elif type == "blob":
 
274
                return self.repository.texts.has_version(type_data)
 
275
            elif type == "tree":
 
276
                return self.repository.has_revision(type_data[1])
69
277
            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):
 
278
                raise AssertionError("Unknown object type '%s'" % type)
 
279
        except KeyError:
 
280
            return False
 
281
        else:
 
282
            return True
 
283
 
 
284
    def _lookup_git_sha(self, sha):
89
285
        # See if sha is in map
90
286
        try:
91
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
287
            return self._idmap.lookup_git_sha(sha)
92
288
        except KeyError:
93
 
            # if not, see if there are any unconverted revisions and add them 
 
289
            # if not, see if there are any unconverted revisions and add them
94
290
            # to the map, search for sha in map again
95
291
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
292
            return self._idmap.lookup_git_sha(sha)
 
293
 
 
294
    def __getitem__(self, sha):
 
295
        (type, type_data) = self._lookup_git_sha(sha)
97
296
        # convert object to git object
98
297
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
298
            try:
 
299
                rev = self.repository.get_revision(type_data[0])
 
300
            except errors.NoSuchRevision:
 
301
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
302
                raise KeyError(sha)
 
303
            return self._get_commit(rev, type_data[1], expected_sha=sha)
100
304
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
305
            return self._get_blob(type_data[0], type_data[1], expected_sha=sha)
102
306
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
307
            try:
 
308
                inv = self.repository.get_inventory(type_data[1])
 
309
                rev = self.repository.get_revision(type_data[1])
 
310
            except errors.NoSuchRevision:
 
311
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
312
                raise KeyError(sha)
 
313
            unusual_modes = extract_unusual_modes(rev)
 
314
            try:
 
315
                return self._get_tree(type_data[0], type_data[1], inv, unusual_modes,
 
316
                                      expected_sha=sha)
 
317
            except errors.NoSuchRevision:
 
318
                raise KeyError(sha)
104
319
        else:
105
320
            raise AssertionError("Unknown object type '%s'" % type)