/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

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