/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

Support submodules during fetch.

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
        elif entry.kind == "file":
 
134
            return self._get_blob(entry.file_id, entry.revision)
 
135
        else:
 
136
            raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
137
 
 
138
    def _get_ie_object_or_sha1(self, entry, inv, unusual_modes):
 
139
        if entry.kind == "directory":
 
140
            try:
 
141
                return self._idmap.lookup_tree(entry.file_id, inv.revision_id), None
 
142
            except KeyError:
 
143
                ret = self._get_ie_object(entry, inv, unusual_modes)
 
144
                if ret is None:
 
145
                    hexsha = None
 
146
                else:
 
147
                    hexsha = ret.id
 
148
                self._idmap.add_entry(hexsha, "tree", (entry.file_id, inv.revision_id))
 
149
                return hexsha, ret
 
150
        elif entry.kind == "file":
 
151
            try:
 
152
                return self._idmap.lookup_blob(entry.file_id, entry.revision), None
 
153
            except KeyError:
 
154
                ret = self._get_ie_object(entry, inv, unusual_modes)
 
155
                self._idmap.add_entry(ret.id, "blob", (entry.file_id, entry.revision))
 
156
                return ret.id, ret
 
157
        else:
 
158
            raise AssertionError("unknown entry kind '%s'" % entry.kind)
 
159
 
 
160
    def _get_ie_sha1(self, entry, inv, unusual_modes):
 
161
        return self._get_ie_object_or_sha1(entry, inv, unusual_modes)[0]
 
162
 
 
163
    def _get_blob(self, fileid, revision, expected_sha=None):
 
164
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
165
        
 
166
        :param fileid: File id of the text
 
167
        :param revision: Revision of the text
 
168
        """
 
169
        chunks = self.repository.iter_files_bytes([(fileid, revision, None)]).next()[1]
77
170
        blob = Blob()
78
 
        blob._text = text
 
171
        blob._text = "".join(chunks)
 
172
        self._check_expected_sha(expected_sha, blob)
79
173
        return blob
80
174
 
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)
 
175
    def _get_tree(self, fileid, revid, inv, unusual_modes, expected_sha=None):
 
176
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
177
 
 
178
        :param fileid: fileid in the tree.
 
179
        :param revision: Revision of the tree.
 
180
        """
 
181
        tree = directory_to_tree(inv[fileid], 
 
182
            lambda ie: self._get_ie_sha1(ie, inv, unusual_modes),
 
183
            unusual_modes)
 
184
        self._check_expected_sha(expected_sha, tree)
 
185
        return tree
 
186
 
 
187
    def _get_commit(self, rev, tree_sha, expected_sha=None):
 
188
        commit = self._revision_to_commit(rev, tree_sha)
 
189
        self._check_expected_sha(expected_sha, commit)
 
190
        return commit
 
191
 
 
192
    def get_parents(self, sha):
 
193
        """Retrieve the parents of a Git commit by SHA1.
 
194
 
 
195
        :param sha: SHA1 of the commit
 
196
        :raises: KeyError, NotCommitError
 
197
        """
 
198
        return self[sha].parents
 
199
 
 
200
    def _lookup_revision_sha1(self, revid):
 
201
        """Return the SHA1 matching a Bazaar revision."""
 
202
        if revid == NULL_REVISION:
 
203
            return "0" * 40
 
204
        try:
 
205
            return self._idmap.lookup_commit(revid)
 
206
        except KeyError:
 
207
            self._update_sha_map(revid)
 
208
            return self._idmap.lookup_commit(revid)
 
209
 
 
210
    def get_raw(self, sha):
 
211
        """Get the raw representation of a Git object by SHA1.
 
212
 
 
213
        :param sha: SHA1 of the git object
 
214
        """
 
215
        obj = self[sha]
 
216
        return (obj.type, obj.as_raw_string())
 
217
 
 
218
    def __contains__(self, sha):
 
219
        # See if sha is in map
 
220
        try:
 
221
            (type, type_data) = self._lookup_git_sha(sha)
 
222
            if type == "commit":
 
223
                return self.repository.has_revision(type_data[0])
 
224
            elif type == "blob":
 
225
                return self.repository.texts.has_version(type_data)
 
226
            elif type == "tree":
 
227
                return self.repository.has_revision(type_data[1])
 
228
            else:
 
229
                raise AssertionError("Unknown object type '%s'" % type)
 
230
        except KeyError:
 
231
            return False
 
232
        else:
 
233
            return True
 
234
 
 
235
    def _lookup_git_sha(self, sha):
 
236
        # See if sha is in map
 
237
        try:
 
238
            return self._idmap.lookup_git_sha(sha)
92
239
        except KeyError:
93
240
            # if not, see if there are any unconverted revisions and add them 
94
241
            # to the map, search for sha in map again
95
242
            self._update_sha_map()
96
 
            (type, type_data) = self._idmap.lookup_git_sha(sha)
 
243
            return self._idmap.lookup_git_sha(sha)
 
244
 
 
245
    def __getitem__(self, sha):
 
246
        (type, type_data) = self._lookup_git_sha(sha)
97
247
        # convert object to git object
98
248
        if type == "commit":
99
 
            return self._get_commit(*type_data)
 
249
            try:
 
250
                rev = self.repository.get_revision(type_data[0])
 
251
            except errors.NoSuchRevision:
 
252
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
253
                raise KeyError(sha)
 
254
            return self._get_commit(rev, type_data[1], expected_sha=sha)
100
255
        elif type == "blob":
101
 
            return self._get_blob(*type_data)
 
256
            return self._get_blob(type_data[0], type_data[1], expected_sha=sha)
102
257
        elif type == "tree":
103
 
            return self._get_tree(*type_data)
 
258
            try:
 
259
                inv = self.repository.get_inventory(type_data[1])
 
260
                rev = self.repository.get_revision(type_data[1])
 
261
            except errors.NoSuchRevision:
 
262
                trace.mutter('entry for %s %s in shamap: %r, but not found in repository', type, sha, type_data)
 
263
                raise KeyError(sha)
 
264
            unusual_modes = extract_unusual_modes(rev)
 
265
            try:
 
266
                return self._get_tree(type_data[0], type_data[1], inv, unusual_modes,
 
267
                                      expected_sha=sha)
 
268
            except errors.NoSuchRevision:
 
269
                raise KeyError(sha)
104
270
        else:
105
271
            raise AssertionError("Unknown object type '%s'" % type)