/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

Keep inventory entry children around once we've fetched them.

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
 
19
from dulwich.objects import (
 
20
    Blob,
 
21
    Tree,
 
22
    hex_to_sha,
 
23
    )
 
24
from dulwich.object_store import (
 
25
    BaseObjectStore,
 
26
    ObjectStoreIterator,
 
27
    )
 
28
import stat
 
29
 
 
30
from bzrlib import (
 
31
    debug,
 
32
    errors,
 
33
    trace,
 
34
    ui,
 
35
    )
 
36
from bzrlib.revision import (
 
37
    NULL_REVISION,
 
38
    )
 
39
 
 
40
from bzrlib.plugins.git.errors import (
 
41
    GhostRevision,
 
42
    )
24
43
 
25
44
from bzrlib.plugins.git.mapping import (
26
 
    inventory_to_tree_and_blobs,
 
45
    default_mapping,
 
46
    directory_to_tree,
 
47
    extract_unusual_modes,
 
48
    mapping_registry,
27
49
    revision_to_commit,
28
50
    )
29
 
from bzrlib.plugins.git.shamap import GitShaMap
30
 
 
31
 
from dulwich.objects import (
32
 
    Blob,
 
51
from bzrlib.plugins.git.shamap import (
 
52
    SqliteGitShaMap,
 
53
    TdbGitShaMap,
33
54
    )
34
55
 
35
56
 
36
 
class GitObjectConverter(object):
 
57
def get_object_store(repo, mapping=None):
 
58
    git = getattr(repo, "_git", None)
 
59
    if git is not None:
 
60
        return git.object_store
 
61
    return BazaarObjectStore(repo, mapping)
 
62
 
 
63
 
 
64
class BazaarObjectStore(BaseObjectStore):
 
65
    """A Git-style object store backed onto a Bazaar repository."""
37
66
 
38
67
    def __init__(self, repository, mapping=None):
39
68
        self.repository = repository
40
69
        if mapping is None:
41
 
            self.mapping = self.repository.get_mapping()
 
70
            self.mapping = default_mapping
42
71
        else:
43
72
            self.mapping = mapping
44
 
        self._idmap = GitShaMap(self.repository._transport)
 
73
        try:
 
74
            self._idmap = TdbGitShaMap.from_repository(repository)
 
75
        except ImportError:
 
76
            self._idmap = SqliteGitShaMap.from_repository(repository)
45
77
 
46
 
    def _update_sha_map(self):
47
 
        all_revids = self.repository.all_revision_ids()
 
78
    def _update_sha_map(self, stop_revision=None):
 
79
        if stop_revision is None:
 
80
            all_revids = self.repository.all_revision_ids()
 
81
        else:
 
82
            all_revids = self.repository.get_ancestry(stop_revision)
 
83
            first = all_revids.pop(0) # Pop leading None
 
84
            assert first is None
48
85
        graph = self.repository.get_graph()
49
86
        present_revids = set(self._idmap.revids())
 
87
        missing_revids = [revid for revid in graph.iter_topo_order(all_revids) if revid not in present_revids]
50
88
        pb = ui.ui_factory.nested_progress_bar()
51
89
        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))
 
90
            for i, revid in enumerate(missing_revids):
 
91
                pb.update("updating git map", i, len(missing_revids))
56
92
                self._update_sha_map_revision(revid)
57
93
        finally:
 
94
            self._idmap.commit()
58
95
            pb.finished()
59
96
 
 
97
    def __iter__(self):
 
98
        self._update_sha_map()
 
99
        return iter(self._idmap.sha1s())
 
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 = revision_to_commit(rev, tree_sha,
 
107
                                        self._idmap.lookup_commit)
 
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
                self._idmap.add_entry(ret.id, "tree", (entry.file_id, inv.revision_id))
 
143
                return ret.id, ret
 
144
        else:
 
145
            try:
 
146
                return self._idmap.lookup_blob(entry.file_id, entry.revision), None
 
147
            except KeyError:
 
148
                ret = self._get_ie_object(entry, inv, unusual_modes)
 
149
                self._idmap.add_entry(ret.id, "blob", (entry.file_id, entry.revision))
 
150
                return ret.id, ret
 
151
 
 
152
    def _get_ie_sha1(self, entry, inv, unusual_modes):
 
153
        return self._get_ie_object_or_sha1(entry, inv, unusual_modes)[0]
 
154
 
 
155
    def _get_blob(self, fileid, revision, expected_sha=None):
 
156
        """Return a Git Blob object from a fileid and revision stored in bzr.
 
157
        
 
158
        :param fileid: File id of the text
 
159
        :param revision: Revision of the text
 
160
        """
 
161
        text = self.repository.texts.get_record_stream([(fileid, revision)],
 
162
            "unordered", True).next().get_bytes_as("fulltext")
77
163
        blob = Blob()
78
164
        blob._text = text
 
165
        self._check_expected_sha(expected_sha, blob)
79
166
        return blob
80
167
 
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)
 
168
    def _get_tree(self, fileid, revid, inv, unusual_modes, expected_sha=None):
 
169
        """Return a Git Tree object from a file id and a revision stored in bzr.
 
170
 
 
171
        :param fileid: fileid in the tree.
 
172
        :param revision: Revision of the tree.
 
173
        """
 
174
        tree = directory_to_tree(inv[fileid], 
 
175
            lambda ie: self._get_ie_sha1(ie, inv, unusual_modes),
 
176
            unusual_modes)
 
177
        self._check_expected_sha(expected_sha, tree)
 
178
        return tree
 
179
 
 
180
    def _get_commit(self, rev, tree_sha, expected_sha=None):
 
181
        try:
 
182
            commit = revision_to_commit(rev, tree_sha, self._lookup_revision_sha1)
 
183
        except errors.NoSuchRevision, e:
 
184
            raise GhostRevision(e.branch, e.revision)
 
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)