/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

Run bzrlib foreign tests.

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