/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 InterTree.iter_changes() as well.

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